Skip to main content

Boilerplate Overview: Java + React

This boilerplate builds the same extension as Node + React — it lists a company's products — on a Spring Boot backend with the shared React frontend. It is the recommended Java combination; a java-vue template also exists, built the same way with a Vue 2 frontend.

Prerequisite

If you do not have the boilerplate, please go through the steps in the Get Started page.

If FDK CLI is already set up, run fdk ext init --template java-react to download the boilerplate explained on this page.

You will also need Java 17 or later and Maven installed.

Directory Structure

├── README.md
├── pom.xml # Maven dependencies
├── mvnw, mvnw.cmd # Maven wrapper
├── fdk.ext.config.json # DO NOT TOUCH. Managed by FDK CLI
├── src
│ ├── main
│ │ ├── java/com/fynd/example/java
│ │ │ ├── ExampleJavaApplication.java # Entry point, extension bean & callbacks
│ │ │ ├── controller
│ │ │ │ ├── PlatformController.java # Your Platform API routes
│ │ │ │ ├── RouteController.java # Forwards launch URLs to the frontend
│ │ │ │ └── WebhookController.java # Webhook delivery endpoint
│ │ │ └── webhook
│ │ │ └── SampleEventHandler.java # A webhook event handler
│ │ └── resources
│ │ ├── application.yml # Extension, webhook & server config
│ │ └── application-prod.yml
│ └── test/ # Backend tests
└── frontend # Git submodule: example-extension-react
The frontend is a submodule

frontend is a Git submodule pointing at example-extension-react — the same React app the Node templates use. FDK CLI clones it during fdk ext init.


Backend

We use the Java Extension Helper Library to handle authentication, call Platform APIs through the SDK, and subscribe to webhooks.

Dependencies

  • fdk-extension-java for interacting with Fynd Commerce.
  • fdk-client-java a peer dependency for the previous library.
  • spring-boot-starter-web for the server, with spring-boot-devtools for reload during development.
  • lombok for the @Slf4j logger and boilerplate reduction.
  • Test dependencies: spring-boot-starter-test, mockito-inline, junit

Both libraries are published to the com.azure.gofynd group; their versions are pinned as properties in pom.xml.

<fdk-extension.version>0.8.1</fdk-extension.version>
<fdk-client.version>3.1.0-beta.6</fdk-client.version>

Auth Credentials

The FDK CLI configures the API key and secret during development when you run fdk ext preview. The credentials are taken from the Partners panel, saved in the fdk.ext.config.json file, and passed to the backend as environment variables.

Find API credentials: Partner panel → Extensions → <your-extension> → Credentials

Configuration

Everything the extension needs is declared in src/main/resources/application.yml and bound to the library's ExtensionProperties. The values come from environment variables, which FDK CLI sets during development.

server:
port: ${BACKEND_PORT:8080}

ext:
api_key: ${EXTENSION_API_KEY}
api_secret: ${EXTENSION_API_SECRET}
scopes: ""
base_url: ${EXTENSION_BASE_URL}
cluster: ${FP_API_DOMAIN:https://api.fynd.com}
webhook:
api_path: "/api/webhook-events"
notification_email: "useremail@example.com"
event_map:
- name: "product/delete"
handler: sampleHandler
category: "company"
version: 1

Sessions are stored in SQLite, configured at the top of the same file:

sqlite:
db:
url: 'jdbc:sqlite:session_storage.db'
Change the notification email

notification_email is where Fynd Commerce writes when a webhook delivery keeps failing. Point it at an address your team actually reads before going live.

Setup FDK

ExampleJavaApplication.java is both the Spring Boot entry point and where the extension is initialised. The Extension bean receives the properties, the session storage, and the callbacks.

@Bean
public com.fynd.extension.model.Extension getExtension() throws ClassNotFoundException {
Extension extension = new Extension();
return extension.initialize(
extensionProperties,
new SQLiteStorage(dbUrl, REDIS_KEY),
callbacks
);
}

REDIS_KEY is the prefix every session key is stored under — set it to your own extension's slug so two extensions never collide.

The callbacks are supplied as an ExtensionCallback with four handlers: auth, install, uninstall and auto-install. The auth handler returns the URL the merchant lands on after installing, and returns two different paths depending on the launch context.

ExtensionCallback callbacks = new ExtensionCallback((request) -> {
Session fdkSession = (Session) request.getAttribute("session");
if (request.getParameter("application_id") != null) {
return extensionProperties.getBaseUrl() + "/company/" + fdkSession.getCompanyId()
+ "/application/" + request.getParameter("application_id");
} else {
return extensionProperties.getBaseUrl() + "/company/" + fdkSession.getCompanyId();
}
}, (context) -> {
logger.info("In install callback");
return extensionProperties.getBaseUrl();
}, (fdkSession) -> {
logger.info("In uninstall callback"); // clean up your data for this company here
return extensionProperties.getBaseUrl();
}, (fdkSession) -> {
logger.info("In auto-install callback");
return extensionProperties.getBaseUrl();
});

The component scan has to cover the library packages as well as your own, or the extension's own routes and filters are never registered.

@SpringBootApplication
@ComponentScan(basePackages = {"com.fynd.**", "com.sdk.**"})
public class ExampleJavaApplication { ... }

API Routing

Extend BasePlatformController and every route in the controller runs behind a valid session, with an authenticated PlatformClient on the request.

@RestController
@RequestMapping("/api")
public class PlatformController extends BasePlatformController {

@GetMapping(value = "/products", produces = "application/json")
public CatalogPlatformModels.ProductListingResponseV2 getProducts(HttpServletRequest request) {
PlatformClient platformClient = (PlatformClient) request.getAttribute("platformClient");
return platformClient.catalog.getProducts(/* … */);
}

// Sales-channel products, for a launch scoped to one storefront
@GetMapping(value = "/{application_id}/products", produces = "application/json")
public CatalogPlatformModels.RawProductListingResponse getAppProducts(
@PathVariable("application_id") String applicationId, HttpServletRequest request) {
PlatformClient platformClient = (PlatformClient) request.getAttribute("platformClient");
return platformClient.application(applicationId).catalog.getAppProducts(/* … */);
}
}
note

The Java SDK takes every query parameter as a positional argument, so calls look longer than their JavaScript equivalents. Pass Collections.emptyList() and "" for the filters you do not need.

Webhook Handler

Two pieces are needed. First a controller on the api_path from application.yml, which hands the request to WebhookService — that is what verifies the delivery really came from Fynd Commerce.

@PostMapping(path = "/api/webhook-events")
public ResponseEntity<Object> receiveWebhookEvents(HttpServletRequest httpServletRequest) {
try {
webhookService.processWebhook(httpServletRequest);
return new ResponseEntity<>(Collections.singletonMap("success", true), HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(Collections.singletonMap("success", false), HttpStatus.BAD_REQUEST);
}
}

Second, a handler bean whose name matches the handler key in the event map.

@Service("sampleHandler")
public class SampleEventHandler implements EventHandler {
@Override
public void handle(String eventName, Object body, String companyId, String applicationId) {
log.info("Event Received : {} for companyId : {}", eventName, companyId);
// code to handle webhook event here
}
}

One handler can serve many events — list it under each entry in event_map.

Frontend routes

The launch URLs are client-side routes, so a hard refresh on them has to return the React app rather than a 404. RouteController forwards both to the root, where the static frontend is served.

@Controller
public class RouteController {
@RequestMapping("/company/{company_id}")
public String redirect() { return "forward:/"; }

@RequestMapping("/company/{company_id}/application/{application_id}")
public String redirectToAppHome() { return "forward:/"; }
}

Frontend

The frontend is the shared example-extension-react submodule — React 18 rendered by Vite, the same app used by the Node templates. See the Frontend section of the Node + React page for the component walkthrough.

Note that it calls /api/products and /api/products/application/:application_id, while this backend exposes the sales-channel route as /api/{application_id}/products. Align the two before the sales-channel launch works.


Was this section helpful?