Quarkus Pi4J
Quarkus extension for integrating Pi4J v4 with Quarkus applications running on Raspberry Pi hardware. Provides CDI-managed GPIO access, named-pin injection, a readiness health check, and mock support for dev/test environments.
Installation
Add the extension to your pom.xml:
<dependency>
<groupId>io.quarkiverse.pi4j</groupId>
<artifactId>quarkus-pi4j</artifactId>
<version>${quarkus-pi4j.version}</version>
</dependency>
Configuration
All properties are prefixed with quarkus.pi4j.
| Property | Default | Description |
|---|---|---|
|
|
Enable or disable the extension entirely. |
|
|
Use Pi4J mock providers instead of real hardware. Recommended in dev, test, and CI. |
|
|
Automatically shut down the Pi4J Context when Quarkus stops. |
|
|
Fail application startup if the Pi4J Context cannot be initialized. |
|
— |
Map a logical name to a GPIO address. Example: |
|
— |
Optional provider ID for digital inputs. Omit to let Pi4J choose. |
|
— |
Optional provider ID for digital outputs. Omit to let Pi4J choose. |
Full reference configuration with all properties and profile overrides:
# --- Pi4J extension ---
# Disable the extension entirely (default: true)
# quarkus.pi4j.enabled=false
# Use mock hardware providers instead of real GPIO (default: false)
# Recommended in dev and test to avoid requiring a Raspberry Pi
quarkus.pi4j.mock=false
# Automatically shut down the Pi4J context on application stop (default: true)
quarkus.pi4j.shutdown=true
# Fail startup if Pi4J cannot initialize (default: true)
# Set false to allow the application to start degraded (health check will report DOWN)
quarkus.pi4j.fail-on-startup-error=true
# Named pins — map a logical name to a BCM GPIO address
# Inject with @NamedGpio("led") or GpioService.namedOutput("led")
quarkus.pi4j.pins.led=13
quarkus.pi4j.pins.button=5
quarkus.pi4j.pins.buzzer=6
# Optional: pin to a specific Pi4J provider ID (omit to let Pi4J choose)
# quarkus.pi4j.digital-input-provider=pigpio-digital-input
# quarkus.pi4j.digital-output-provider=pigpio-digital-output
# --- Profile overrides ---
# Use mock providers in dev and test — no hardware required
%dev.quarkus.pi4j.mock=true
%test.quarkus.pi4j.mock=true
Usage
The example below shows all three injection styles together in a single bean:
package com.example;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import com.pi4j.context.Context;
import com.pi4j.io.gpio.digital.DigitalInput;
import com.pi4j.io.gpio.digital.DigitalOutput;
import io.quarkiverse.pi4j.gpio.GpioPin;
import io.quarkiverse.pi4j.gpio.GpioService;
import io.quarkiverse.pi4j.gpio.NamedGpio;
import io.quarkus.runtime.StartupEvent;
@ApplicationScoped
public class Pi4jApplication {
// Raw Pi4J context — use when you need provider-level access
@Inject
Context pi4j;
// Inject a DigitalOutput by BCM address
@Inject
@GpioPin(13)
DigitalOutput led;
// Inject a DigitalInput by BCM address with explicit id and name
@Inject
@GpioPin(value = 5, id = "button", name = "Push Button")
DigitalInput button;
// Inject by logical name — requires quarkus.pi4j.pins.buzzer=<address>
@Inject
@NamedGpio("buzzer")
DigitalOutput buzzer;
// Imperative API — useful when the address is only known at runtime
@Inject
GpioService gpio;
void onStart(@Observes StartupEvent event) {
led.low();
buzzer.low();
button.addListener(e -> {
if (e.state().isHigh()) {
led.high();
} else {
led.low();
}
});
}
public void blink() {
led.high();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
led.low();
}
public void togglePin(int address) {
DigitalOutput out = gpio.output(address);
if (out.isHigh())
out.low();
else
out.high();
}
}
GPIO by pin address
Use @GpioPin to inject a DigitalOutput or DigitalInput directly by BCM address:
@Inject @GpioPin(13)
DigitalOutput led;
@Inject @GpioPin(value = 5, id = "button", name = "Push Button")
DigitalInput button;
GPIO by configured name
Use @NamedGpio to inject a pin by its logical name defined in application.properties:
// requires quarkus.pi4j.pins.led=13
@Inject @NamedGpio("led")
DigitalOutput led;
// requires quarkus.pi4j.pins.button=5
@Inject @NamedGpio("button")
DigitalInput button;
Imperative GPIO via GpioService
GpioService provides an imperative API for creating pins at runtime:
@Inject
GpioService gpio;
DigitalOutput out = gpio.output(13);
DigitalInput in = gpio.input(5);
DigitalOutput led = gpio.namedOutput("led");
DigitalInput button = gpio.namedInput("button");
Pins are cached by ID — calling output(13) twice returns the same instance.
Health Check
When quarkus-smallrye-health is on the classpath, the extension registers a @Readiness health check named pi4j.
{
"name": "pi4j",
"status": "UP",
"data": {
"enabled": true,
"mock": false
}
}
If the Context fails to start, the check reports DOWN with the error class and message.
Testing
Enable mock providers in the test profile so tests run without Raspberry Pi hardware:
%test.quarkus.pi4j.mock=true
Full @QuarkusTest example covering context availability, pin toggling, and named GPIO resolution:
package com.example;
import static org.junit.jupiter.api.Assertions.*;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import com.pi4j.context.Context;
import com.pi4j.io.gpio.digital.DigitalOutput;
import io.quarkiverse.pi4j.gpio.GpioService;
import io.quarkiverse.pi4j.gpio.NamedGpio;
import io.quarkus.test.junit.QuarkusTest;
// %test.quarkus.pi4j.mock=true must be set in src/test/resources/application.properties
@QuarkusTest
class Pi4jQuarkusTest {
@Inject
Context pi4j;
@Inject
GpioService gpio;
@Inject
@NamedGpio("led")
DigitalOutput led;
@Test
void contextIsAvailable() {
assertNotNull(pi4j);
}
@Test
void ledStartsLow() {
led.low();
assertTrue(led.isLow());
}
@Test
void outputCanToggle() {
DigitalOutput out = gpio.output(13);
out.high();
assertTrue(out.isHigh());
out.low();
assertTrue(out.isLow());
}
@Test
void namedOutputResolvesAddress() {
// resolves quarkus.pi4j.pins.led=13 from application.properties
DigitalOutput namedLed = gpio.namedOutput("led");
assertNotNull(namedLed);
}
}