diff --git a/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/MapController.java b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/MapController.java
new file mode 100644
index 0000000000000000000000000000000000000000..ef89ab0ba4fe7a5ea9e4fe0bab3f69fad34b9e6e
--- /dev/null
+++ b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/MapController.java
@@ -0,0 +1,94 @@
+package gov.usgs.earthquake.nshmp.netcdf.www;
+
+import java.nio.file.Path;
+
+import gov.usgs.earthquake.nshmp.geo.json.FeatureCollection;
+import gov.usgs.earthquake.nshmp.netcdf.NetcdfDataFilesHazardCurves;
+import gov.usgs.earthquake.nshmp.netcdf.www.FeatureCollectionService.RequestData;
+import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
+import gov.usgs.earthquake.nshmp.www.ResponseBody;
+
+import io.micronaut.context.annotation.Value;
+import io.micronaut.context.event.StartupEvent;
+import io.micronaut.core.annotation.Nullable;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Get;
+import io.micronaut.http.annotation.PathVariable;
+import io.micronaut.http.annotation.QueryValue;
+import io.micronaut.runtime.event.annotation.EventListener;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Inject;
+
+@Tag(name = MapController.NAME)
+@Controller("/map")
+public class MapController {
+  static final String NAME = "Map Boundary";
+
+  @Inject
+  private NshmpMicronautServlet servlet;
+
+  /**
+   * The path to a hazard NetCDF file
+   */
+  @Value("${nshmp-ws-static.netcdf-path}")
+  Path netcdfPath;
+
+  NetcdfServiceHazardCurves service;
+
+  /**
+   * Read in data type and return the appropriate service to use.
+   */
+  @EventListener
+  void startup(StartupEvent event) {
+    var netcdfDataFiles = new NetcdfDataFilesHazardCurves(netcdfPath);
+    service = new NetcdfServiceHazardCurves(netcdfDataFiles);
+  }
+
+  @Operation(
+      summary = "Get the GeoJSON map boundary",
+      description = "Returns the feature collection of NSHM map boundary",
+      operationId = "map")
+  @ApiResponse(
+      description = "NSHM map boundary",
+      responseCode = "200",
+      content = @Content(
+          schema = @Schema(implementation = Response.class)))
+  @Get(uri = "{?raw}", produces = MediaType.APPLICATION_JSON)
+  public HttpResponse<String> doGet(
+      HttpRequest<?> http,
+      @QueryValue(defaultValue = "false") @Nullable Boolean raw) {
+    try {
+      FeatureCollection map = service.netcdf().netcdfData().map();
+      return HttpResponse
+          .ok(FeatureCollectionService.handleFeatureCollection(http, NAME, map, raw));
+    } catch (Exception e) {
+      return NetcdfWsUtils.handleError(e, NAME, http.getUri().toString());
+    }
+  }
+
+  @Operation(
+      summary = "Get the GeoJSON map boundary",
+      description = "Returns the feature collection of NSHM map boundary",
+      operationId = "map-slash")
+  @ApiResponse(
+      description = "NSHM map boundary",
+      responseCode = "200",
+      content = @Content(
+          schema = @Schema(implementation = Response.class)))
+  @Get(uri = "/{raw}", produces = MediaType.APPLICATION_JSON)
+  public HttpResponse<String> doGetSlash(
+      HttpRequest<?> http,
+      @PathVariable(defaultValue = "false") @Nullable Boolean raw) {
+    return doGet(http, raw);
+  }
+
+  // Swagger schema
+  private static class Response extends ResponseBody<RequestData, FeatureCollection> {};
+}