Guides

Spring Boot integration

A complete walkthrough of integrating confiqure.ai into a Spring Boot 3+ application: add the annotation, mint embed tokens from a service, and receive callbacks into a controller.


1. Add the annotation dependency

Pull in confiqure-annotation-java from Maven Central — no extra repository entry needed.

pom.xml
<dependency>
  <groupId>ai.confiqure</groupId>
  <artifactId>confiqure-annotation-java</artifactId>
  <version>3.0.0</version>
</dependency>

The library uses an annotation processor that hooks into javac's internal APIs (same mechanism as Lombok) to inject a backend-assigned confiqureKey field plus getter/setter into each annotated class. Add these compiler args so the processor can run on JDK 11+.

pom.xml (build / plugins)
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <fork>true</fork>
    <compilerArgs>
      <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
      <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
      <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
      <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
      <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
      <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
    </compilerArgs>
  </configuration>
</plugin>

Without these args, the processor silently skips injection and the confiqureKey round-trip won't work. The -J prefix passes the flag to the forked javac JVM rather than to javac itself, which is why <fork>true</fork> is required.

2. Annotate a configuration class

Pick a class that represents user-tunable settings. Use plain Java types — confiqure handles enums, lists, and nested classes natively.

NotificationPreferences.java
package com.example.notifications;

import ai.confiqure.Confiqure;
import java.util.List;

@Confiqure.Setting(end = "/notifications")
public class NotificationPreferences {

  // Which channels should receive alerts?
  private List<Channel> channels;

  // How often should digest emails be sent?
  private Frequency digestFrequency;

  // Getters/setters omitted.
}

3. Push the schema

Terminal
$ git commit -am "add notification config"
$ npx @confiqure/cli push

Push targets your sandbox workspace; promote with push --production when the conversation behaves — details on the push page.

4. Mint embed tokens from a service

Store your cqai_… key in application.properties as a secret. A thin RestClient handles the mint. Send the attachments block on every mint: without it the widget shows no attach button, no paste and no ctrl+v.

ConfiqureClient.java
@Service
public class ConfiqureClient {

  private final RestClient http;
  private final String workspaceKey;
  private final String apiKey;

  public ConfiqureClient(
    @Value("${confiqure.workspace-key}") String workspaceKey,
    @Value("${confiqure.api-key}")       String apiKey
  ) {
    this.workspaceKey = workspaceKey;
    this.apiKey       = apiKey;
    this.http = RestClient.builder()
        .baseUrl("https://api.confiqure.ai")
        .build();
  }

  public EmbedToken mint(String userId) {
    return http.post()
      .uri("/api/{k}/embed-tokens", workspaceKey)
      .header("Authorization", "Bearer " + apiKey)
      .contentType(MediaType.APPLICATION_JSON)
      .body(Map.of(
        "endUserHandle", userId,
        "attachments",   Map.of("enabled", true,
                                "allowedTypes", List.of("image/png", "image/jpeg"),
                                "camera", true),
        "ttlSeconds",    3600
      ))
      .retrieve()
      .body(EmbedToken.class);
  }

  public record EmbedToken(String token, String jti, Instant expiresAt) {}
}

5. Expose a "give-me-a-token" endpoint

Your authenticated frontend calls this when it needs to mount the widget. The endpoint must verify the caller and only mint a token for their own endUserHandle — never trust a userId from the request body.

ConfiqureWidgetController.java
@RestController
@RequestMapping("/api/widget")
public class ConfiqureWidgetController {

  private final ConfiqureClient confiqure;

  @PostMapping("/notifications/token")
  public EmbedToken token(Authentication auth) {
    String userId = auth.getName();
    return confiqure.mint(userId);
  }
}

6. Receive the callback

Mark one controller method with @Confiqure.DefaultCallbackHook — its route is discovered on push and combined with the host base URL you set once in Dashboard settings. The payload carries confiqureKeys, not values: on onComplete, fetch each key and bind its data into your class.

ConfiqureCallbackController.java
@RestController
@RequestMapping("/api/confiqure")
public class ConfiqureCallbackController {

  private final NotificationPreferencesService prefs;
  private final ObjectMapper json;

  @Confiqure.DefaultCallbackHook
  @PostMapping("/callback")
  public ResponseEntity<Void> receive(@RequestBody CallbackPayload body) {
    // One endpoint, every lifecycle event — branch on `event`.
    if (!"onComplete".equals(body.event())) {
      return ResponseEntity.ok().build();   // onStart / session closes: ack and ignore
    }
    // onComplete carries confiqureKeys, not values — pull each instance and bind its `data`.
    for (String key : body.confiqureKeys()) {
      Map<String, Object> data = confiqure.fetchData(body.configEnd(), body.endUserHandle(), key);
      prefs.save(body.endUserHandle(), json.convertValue(data, NotificationPreferences.class));
    }
    return ResponseEntity.ok().build();
  }

  public record CallbackPayload(
    String event,
    String workspaceKey,
    String configEnd,
    String endUserHandle,
    Long   conversationId,
    List<String> confiqureKeys,
    String timestamp
  ) {}
}

application.properties

confiqure.workspace-key=abcdef
confiqure.api-key=${CONFIQURE_API_KEY}

That's the whole integration

No settings forms, no CRUD endpoints, no client-side validation. Two controllers, one service, one annotated class.

Last updated May 2026