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

quarkus.pi4j.enabled

true

Enable or disable the extension entirely.

quarkus.pi4j.mock

false

Use Pi4J mock providers instead of real hardware. Recommended in dev, test, and CI.

quarkus.pi4j.shutdown

true

Automatically shut down the Pi4J Context when Quarkus stops.

quarkus.pi4j.fail-on-startup-error

true

Fail application startup if the Pi4J Context cannot be initialized.

quarkus.pi4j.pins.<name>

Map a logical name to a GPIO address. Example: quarkus.pi4j.pins.led=13.

quarkus.pi4j.digital-input-provider

Optional provider ID for digital inputs. Omit to let Pi4J choose.

quarkus.pi4j.digital-output-provider

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();
    }
}

Injecting the Pi4J Context

@Inject
Context pi4j;

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);
    }
}