
QRs are everywhere. Be it the floating Super Bowl ad by CoinBase to encourage user signups. Or to rickroll the entire internet.
It might look like just a black and white image — but in essence it’s powerful.
People say, Java developers live under a rock. But I’m sure that you’re not one of them. That’s why you crash-landed onto this article.
Let’s make something cool with AWS Lambda.
Java has never been that easy. But you’re going to have it easier.
Let’s start by creating an empty Java project!
We’ll be using Maven because of the popularity and ease of use. You can also use other package managers like Gradle or Groovy.
You don’t need to use any IDE, you can make this even in Notepad. Run the following command to create a boilerplate with Maven:
mvn archetype:generate -DgroupId=io.learnaws -DartifactId=qr-generator -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
groupId
should becom.{group-name}
artifactId
should be{project-name}
Leave the other parameters as-is.
You’ll get an output in your terminal something like this:
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.2.1:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.2.1:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.2.1:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.0
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: basedir, Value: /home/shivam/learnaws
[INFO] Parameter: package, Value: io.learnaws
[INFO] Parameter: groupId, Value: io.learnaws
[INFO] Parameter: artifactId, Value: qr-generator
[INFO] Parameter: packageName, Value: io.learnaws
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] project created from Old (1.x) Archetype in dir: /home/shivam/learnaws/qr-generator
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.729 s
[INFO] Finished at: 2023-06-26T21:40:55+05:30
[INFO] ------------------------------------------------------------------------
Maven will create all the necessary files and directories depending on the package name similar to this:
├── pom.xml
└── src
├── main
│ └── java
│ └── io
│ └── learnaws
│ └── App.java
└── test
└── java
└── io
└── learnaws
└── AppTest.java
Now we need to install AWS dependencies:
Simply add the AWS Lambda Core, Lambda Events dependencies and Maven Shade plugin in your pom.xml
We need them to easily interface our request and response, more on that later.
<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/maven-v4_0_0.xsd">
....
<!-- add maven compiler -->
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!-- Add plugins inside the build -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- Your AWS deps goes here-->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>3.11.0</version>
</dependency>
</dependencies>
</project>
Editing the main class file
Create your App.java
’s function handleRequest
like this:
package io.learnaws;
import java.util.HashMap;
// Import AWS dependencies
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse;
// We impement RequestHandler with API Gateway V2 cause Function URL uses the same
public class App implements RequestHandler<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse> {
@Override
// Our main function name is handleRequest. We'll need this later.
public APIGatewayV2HTTPResponse handleRequest(APIGatewayV2HTTPEvent event, Context context) {
// instantiate a new APIGWV2 response
APIGatewayV2HTTPResponse response = new APIGatewayV2HTTPResponse();
// we are not encoding our content to base64 so we'll set it false
response.setIsBase64Encoded(false);
// your status code goes here eg: 200, 404, 401
response.setStatusCode(200);
// to set the headers we need a string to string hashmap
HashMap<String, String> headers = new HashMap<String, String>();
// if you don't set the content type it'll be default to application/json
headers.put("Content-Type", "text/html");
// finally set the headers in response
response.setHeaders(headers);
// Now finally set the body of the response as HTML string
response.setBody("<h1>Hello from LearnAWS.io</h1>");
// at last you just return the response from your handler
return response;
}
}
Bundling/Packaging the code for Lambda
Since it’s not JavaScript or Python we will have to compile it into a jar
file so that AWS Lambda can run it in their runner.
Simply run mvn package
, a jar file will be created at /src/main/resources
.
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< io.learnaws:qr-generator >----------------------
[INFO] Building qr-generator 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ qr-generator ---
[INFO] skip non existing resourceDirectory /home/shivam/learnaws/java-cdk/qr-generator/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ qr-generator ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ qr-generator ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /home/shivam/learnaws/java-cdk/qr-generator/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ qr-generator ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ qr-generator ---
[INFO] Surefire report directory: /home/shivam/learnaws/java-cdk/qr-generator/target/surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running io.learnaws.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.006 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ qr-generator ---
[INFO]
[INFO] --- maven-shade-plugin:3.2.2:shade (default) @ qr-generator ---
[INFO] Including com.amazonaws:aws-lambda-java-core:jar:1.2.2 in the shaded jar.
[INFO] Including com.amazonaws:aws-lambda-java-events:jar:3.11.0 in the shaded jar.
[INFO] Including joda-time:joda-time:jar:2.6 in the shaded jar.
[INFO] Including io.nayuki:qrcodegen:jar:1.8.0 in the shaded jar.
[WARNING] Discovered module-info.class. Shading will break its strong encapsulation.
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing /home/shivam/learnaws/java-cdk/qr-generator/target/qr-generator-1.0-SNAPSHOT.jar with /home/shivam/learnaws/java-cdk/qr-generator/target/qr-generator-1.0-SNAPSHOT-shaded.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.904 s
[INFO] Finished at: 2023-07-13T16:14:22+05:30
[INFO] ------------------------------------------------------------------------
Create a Java 17 Lambda function
To create a new function (console)
- Open the Functions page of the Lambda console and choose Create Function.
- Choose Author from scratch.

- Under Basic information, do the following:
- For Function name, enter the name for your function.
- For Runtime, select the runtime you want to use, in this case select Java.
- (Optional) For Architecture, choose the instruction set architecture for your function. The default architecture is x86_64. Ensure that the .zip deployment package for your function is compatible with the instruction set architecture you select.
- Under Advanced Settings, check Enable function URL.

- Set Auth type to NONE.
- Keep the Invoke mode to BUFFERED (default).
- Choose Create function. Lambda creates a basic ‘Hello world’ function using your chosen runtime.
You can follow this guide from AWS if you want to use groovy.
Upload the jar
from Lambda console

Select the Upload from → .zip or .jar file.
Now drag and drop your .jar from inside your target
folder.
(NOT the one which says original)


You’ll be greeted with the following message in a green banner:
Successfully updated the function lambda-qr-gen.
Update your handler
Updating your handler ensures that AWS Lambda finds where your function is located and serves exactly that.
Go to the Runtime Settings section and click on the Edit button.
In the Handler, change example.Handler to your groupId.artifactId::functionName
.
It would look like com.package_name.FileName::yourHandlerFunction
.
Hit Save.

Boost performance by enabling SnapStart (optional)
You can reduce the startup time of your Lambda at no cost.
Go to Configuration tab and click the Edit button.
Change the value of SnapStart from None to PublishedVersions from the drop-down menu.
Hit Save.

Enjoy a faster startup experience as your Lambda snaps back into action from memory instead of HDD.
Testing your Lambda function
If you expand the Function overview, on the right you’ll see the Function URL. Just click on it.

Et voila! You’ll get to see the text “Hello from LearnAWS” or whatever you put in your body on your screen.
You just deployed your first Lambda function. Give yourself a pat 🐾You worked hard for this.

Let’s get your QR code up and running!
I found the best QR Code generator library for Java.
It’s also available in 6 other languages (JavaScript, TypeScript, Python, Rust, C++ and C).
This QR Code generator library is made by Nayuki, kudos to the amazing functions and example she wrote. Leave a start on GitHub: https://github.com/nayuki/QR-Code-generator
Add this as a dependency to your pom.xml
:
<dependency>
<groupId>io.nayuki</groupId>
<artifactId>qrcodegen</artifactId>
<version>1.8.0</version>
</dependency>
Add QR code generator helper methods
Create QrUtils.java
Just like you have App.java
, create another file called QrUtils.java
.
This will contain our helper functions needed to generate the QR code using the qrcodegen
library.
Taken from https://github.com/nayuki/QR-Code-generator/blob/master/java/QrCodeGeneratorDemo.java
package example;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import javax.imageio.ImageIO;
import io.nayuki.qrcodegen.QrCode;
public class QrUtils {
/*---- Utilities ----*/
public static BufferedImage toImage(QrCode qr, int scale, int border) {
return toImage(qr, scale, border, 0xFFFFFF, 0x000000);
}
/**
* Returns a raster image depicting the specified QR Code, with
* the specified module scale, border modules, and module colors.
* <p>
* For example, scale=10 and border=4 means to pad the QR Code with 4 light
* border
* modules on all four sides, and use 10×10 pixels to represent each
* module.
*
* @param qr the QR Code to render (not {@code null})
* @param scale the side length (measured in pixels, must be positive) of
* each module
* @param border the number of border modules to add, which must be
* non-negative
* @param lightColor the color to use for light modules, in 0xRRGGBB format
* @param darkColor the color to use for dark modules, in 0xRRGGBB format
* @return a new image representing the QR Code, with padding and scaling
* @throws NullPointerException if the QR Code is {@code null}
* @throws IllegalArgumentException if the scale or border is out of range, or
* if
* {scale, border, size} cause the image
* dimensions to exceed Integer.MAX_VALUE
*/
public static BufferedImage toImage(QrCode qr, int scale, int border, int lightColor, int darkColor) {
Objects.requireNonNull(qr);
if (scale <= 0 || border < 0)
throw new IllegalArgumentException("Value out of range");
if (border > Integer.MAX_VALUE / 2 || qr.size + border * 2L > Integer.MAX_VALUE / scale)
throw new IllegalArgumentException("Scale or border too large");
BufferedImage result = new BufferedImage((qr.size + border * 2) * scale, (qr.size + border * 2) * scale,
BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < result.getHeight(); y++) {
for (int x = 0; x < result.getWidth(); x++) {
boolean color = qr.getModule(x / scale - border, y / scale - border);
result.setRGB(x, y, color ? darkColor : lightColor);
}
}
return result;
}
// Helper function to reduce code duplication.
public static void writePng(BufferedImage img, String filepath) throws IOException {
ImageIO.write(img, "png", new File(filepath));
}
/**
* Returns a string of SVG code for an image depicting the specified QR Code,
* with the specified
* number of border modules. The string always uses Unix newlines (\n),
* regardless of the platform.
*
* @param qr the QR Code to render (not {@code null})
* @param border the number of border modules to add, which must be
* non-negative
* @param lightColor the color to use for light modules, in any format supported
* by CSS, not {@code null}
* @param darkColor the color to use for dark modules, in any format supported
* by CSS, not {@code null}
* @return a string representing the QR Code as an SVG XML document
* @throws NullPointerException if any object is {@code null}
* @throws IllegalArgumentException if the border is negative
*/
public static String toSvgString(QrCode qr, int border, String lightColor, String darkColor) {
Objects.requireNonNull(qr);
Objects.requireNonNull(lightColor);
Objects.requireNonNull(darkColor);
if (border < 0)
throw new IllegalArgumentException("Border must be non-negative");
long brd = border;
StringBuilder sb = new StringBuilder()
.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
.append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n")
.append(String.format(
"<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 %1$d %1$d\" stroke=\"none\">\n",
qr.size + brd * 2))
.append("\t<rect width=\"100%\" height=\"100%\" fill=\"" + lightColor + "\"/>\n")
.append("\t<path d=\"");
for (int y = 0; y < qr.size; y++) {
for (int x = 0; x < qr.size; x++) {
if (qr.getModule(x, y)) {
if (x != 0 || y != 0)
sb.append(" ");
sb.append(String.format("M%d,%dh1v1h-1z", x + brd, y + brd));
}
}
}
return sb
.append("\" fill=\"" + darkColor + "\"/>\n")
.append("</svg>\n")
.toString();
}
}
Get the route path from API Request
// Add a default QR text
String qrText = "Add your text after the /";
// Remove the first slash (/) from the path
String routePath = event.getRawPath().substring(1);
// Update the QR text if route is not empty
if (routePath.trim() != "") {
// Decode the URI to look like 'cute bird' instead of 'cute%20bird'
qrText = URLDecoder.decode(routePath, StandardCharsets.UTF_8);
}
Generate and update the response body with SVG QR
try {
// first we encode the text by passing text and error correction
QrCode qr0 = QrCode.encodeText(qrText, QrCode.Ecc.MEDIUM);
// using the utils we convert it to SVG or PNG
String svg = QrUtils.toSvgString(qr0, 2, "#ffffff", "#000000");
// set the SVG or png as string in response body
response.setBody(svg);
} catch (Exception e) {
e.printStackTrace();
}
Final code looks:
package io.learnaws;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import javax.imageio.ImageIO;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse;
import io.nayuki.qrcodegen.QrCode;
public class GenerateQr implements RequestHandler<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse> {
@Override
public APIGatewayV2HTTPResponse handleRequest(APIGatewayV2HTTPEvent event, Context context) {
APIGatewayV2HTTPResponse response = new APIGatewayV2HTTPResponse();
String qrText = "Add your text after the /";
String routePath = event.getRawPath().substring(1);
if (routePath.trim() != "") {
qrText = URLDecoder.decode(routePath, StandardCharsets.UTF_8);
}
try {
QrCode qr0 = QrCode.encodeText(qrText, QrCode.Ecc.MEDIUM);
String svg = QrUtils.toSvgString(qr0, 2, "#ffffff", "#000000");
response.setBody(svg);
response.setIsBase64Encoded(false);
response.setStatusCode(200);
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "image/svg+xml");
response.setHeaders(headers);
} catch (Exception e) {
e.printStackTrace();
}
return response;
}
}
Run mvn package
& upload a new JDK
Now we repeat the same step which we did earlier for bundling and uploading the .jar
file to AWS Lambda console.
Get the complete example and code
Complete source code including PNG generation example on this GitHub repo.
Let’s put our QR to the test 🧪
Final test by visiting the same {function URL}/your qr text
which you got earlier.
If you don’t add anything after /
you’ll see the default text.
Open your camera and simply point it towards the QR:
