diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml
index 9df4f52f5df..e5c236da510 100644
--- a/hapi-deployable-pom/pom.xml
+++ b/hapi-deployable-pom/pom.xml
@@ -4,7 +4,7 @@
     <parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml
index a03a6465c2a..29daf3ce599 100644
--- a/hapi-fhir-android/pom.xml
+++ b/hapi-fhir-android/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml
index 912fe4efd95..d841d0053e9 100644
--- a/hapi-fhir-base/pom.xml
+++ b/hapi-fhir-base/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeDeclaredChildDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeDeclaredChildDefinition.java
index 3008324e443..e0dc2a4dceb 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeDeclaredChildDefinition.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/BaseRuntimeDeclaredChildDefinition.java
@@ -22,6 +22,7 @@ package ca.uhn.fhir.context;
 
 import ca.uhn.fhir.model.api.annotation.Child;
 import ca.uhn.fhir.model.api.annotation.Description;
+import ca.uhn.fhir.util.ParametersUtil;
 import ca.uhn.fhir.util.ValidateUtil;
 import org.apache.commons.lang3.Validate;
 import org.hl7.fhir.instance.model.api.IBase;
@@ -60,7 +61,7 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil
 		myElementName = theElementName;
 		if (theDescriptionAnnotation != null) {
 			myShortDefinition = theDescriptionAnnotation.shortDefinition();
-			myFormalDefinition = theDescriptionAnnotation.formalDefinition();
+			myFormalDefinition = ParametersUtil.extractDescription(theDescriptionAnnotation);
 		} else {
 			myShortDefinition = null;
 			myFormalDefinition = null;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Description.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Description.java
index 21cc111e9c6..ecb5317a5f6 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Description.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/Description.java
@@ -30,17 +30,33 @@ import java.lang.annotation.Target;
  * a search parameter definition in order to provide documentation for that item.
  */
 @Retention(RetentionPolicy.RUNTIME)
-@Target(value= {ElementType.FIELD, ElementType.TYPE, ElementType.PARAMETER, ElementType.METHOD})
+@Target(value = {ElementType.FIELD, ElementType.TYPE, ElementType.PARAMETER, ElementType.METHOD})
 public @interface Description {
 
 	/**
-	 * Optional short name for this child 
+	 * A description of this method or parameter
+	 *
+	 * @since 5.4.0
+	 */
+	String value() default "";
+
+	/**
+	 * Optional short name for this child
 	 */
 	String shortDefinition() default "";
-	
+
 	/**
 	 * Optional formal definition for this child
+	 *
+	 * @deprecated Use {@link #value()} instead. Deprecated in 5.4.0.
 	 */
+	@Deprecated
 	String formalDefinition() default "";
-	
+
+	/**
+	 * May be used to supply example values for this
+	 *
+	 * @since 5.4.0
+	 */
+	String[] example() default {};
 }
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/AddTags.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/AddTags.java
index efb3959e40f..00999343985 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/AddTags.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/AddTags.java
@@ -78,4 +78,13 @@ public @interface AddTags {
 	 */
 	Class<? extends IBaseResource> type() default IBaseResource.class;
 
+	/**
+	 * This method allows the return type for this method to be specified in a
+	 * non-type-specific way, using the text name of the resource, e.g. "Patient".
+	 *
+	 * This attribute should be populate, or {@link #type()} should be, but not both.
+	 *
+	 * @since 5.4.0
+	 */
+	String typeName() default "";
 }
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Create.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Create.java
index 2d0ab2811b9..f00f6af8e59 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Create.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Create.java
@@ -45,6 +45,15 @@ public @interface Create {
 	 */
 	// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
 	Class<? extends IBaseResource> type() default IBaseResource.class;
-	
 
+
+	/**
+	 * This method allows the return type for this method to be specified in a
+	 * non-type-specific way, using the text name of the resource, e.g. "Patient".
+	 *
+	 * This attribute should be populate, or {@link #type()} should be, but not both.
+	 *
+	 * @since 5.4.0
+	 */
+	String typeName() default "";
 }
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Delete.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Delete.java
index 33985849f09..82d7e4f1ac8 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Delete.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Delete.java
@@ -47,5 +47,16 @@ public @interface Delete {
 	 * for client implementations. 
 	 */
 	// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
-	Class<? extends IBaseResource> type() default IBaseResource.class;	
+	Class<? extends IBaseResource> type() default IBaseResource.class;
+
+	/**
+	 * This method allows the return type for this method to be specified in a
+	 * non-type-specific way, using the text name of the resource, e.g. "Patient".
+	 *
+	 * This attribute should be populate, or {@link #type()} should be, but not both.
+	 *
+	 * @since 5.4.0
+	 */
+	String typeName() default "";
+
 }
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/DeleteTags.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/DeleteTags.java
index cfed68e45cb..202e82f7c14 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/DeleteTags.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/DeleteTags.java
@@ -74,4 +74,14 @@ public @interface DeleteTags {
 	 */
 	Class<? extends IBaseResource> type() default IBaseResource.class;
 
+	/**
+	 * This method allows the return type for this method to be specified in a
+	 * non-type-specific way, using the text name of the resource, e.g. "Patient".
+	 *
+	 * This attribute should be populate, or {@link #type()} should be, but not both.
+	 *
+	 * @since 5.4.0
+	 */
+	String typeName() default "";
+
 }
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/History.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/History.java
index 6f43678de2f..cd2e0591838 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/History.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/History.java
@@ -80,5 +80,15 @@ public @interface History {
 	 * for information on usage patterns.  
 	 */
 	Class<? extends IBaseResource> type() default IBaseResource.class;
-	
+
+	/**
+	 * This method allows the return type for this method to be specified in a
+	 * non-type-specific way, using the text name of the resource, e.g. "Patient".
+	 *
+	 * This attribute should be populate, or {@link #type()} should be, but not both.
+	 *
+	 * @since 5.4.0
+	 */
+	String typeName() default "";
+
 }
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Operation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Operation.java
index 05f2b59f176..d7b1129dd33 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Operation.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Operation.java
@@ -132,7 +132,10 @@ public @interface Operation {
 
 	/**
 	 * If this is set to <code>true</code>, this method will be a <b>global operation</b>
-	 * meaning that it applies to all resource types
+	 * meaning that it applies to all resource types. Operations with this flag set should be
+	 * placed in Plain Providers (i.e. they don't need to be placed in a resource-type-specific
+	 * <code>IResourceProvider</code> instance) and should have a parameter annotated with
+	 * {@link IdParam}.
 	 */
 	boolean global() default false;
 
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OperationParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OperationParam.java
index f807cb10613..6a9af947295 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OperationParam.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OperationParam.java
@@ -39,7 +39,7 @@ public @interface OperationParam {
 	/**
 	 * Value for {@link OperationParam#max()} indicating no maximum
 	 */
-	final int MAX_UNLIMITED = -1;
+	int MAX_UNLIMITED = -1;
 
 
 	/**
@@ -57,7 +57,7 @@ public @interface OperationParam {
 	 * 
 	 * @since 1.5
 	 */
-	final int MAX_DEFAULT = -2;
+	int MAX_DEFAULT = -2;
 	
 	/**
 	 * The name of the parameter
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Patch.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Patch.java
index 25db4fc6b3b..2e788cba624 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Patch.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Patch.java
@@ -50,4 +50,13 @@ public @interface Patch {
 	// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
 	Class<? extends IBaseResource> type() default IBaseResource.class;
 
+	/**
+	 * This method allows the return type for this method to be specified in a
+	 * non-type-specific way, using the text name of the resource, e.g. "Patient".
+	 *
+	 * This attribute should be populate, or {@link #type()} should be, but not both.
+	 *
+	 * @since 5.4.0
+	 */
+	String typeName() default "";
 }
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Search.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Search.java
index 7f7b0b3799b..3ba92da4e75 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Search.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Search.java
@@ -78,6 +78,16 @@ public @interface Search {
 	// NB: Read, Search (maybe others) share this annotation method, so update the javadocs everywhere
 	Class<? extends IBaseResource> type() default IBaseResource.class;
 
+	/**
+	 * This method allows the return type for this method to be specified in a
+	 * non-type-specific way, using the text name of the resource, e.g. "Patient".
+	 *
+	 * This attribute should be populate, or {@link #type()} should be, but not both.
+	 *
+	 * @since 5.4.0
+	 */
+	String typeName() default "";
+
 	/**
 	 * In a REST server, should this method be invoked even if it does not have method parameters 
 	 * which correspond to all of the URL parameters passed in by the client (default is <code>false</code>).
@@ -91,4 +101,5 @@ public @interface Search {
 	 * </p>
 	 */
 	boolean allowUnknownParams() default false;
+
 }
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Update.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Update.java
index 4392f5d6df5..763f44c8956 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Update.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Update.java
@@ -44,9 +44,19 @@ public @interface Update {
 	 * The return type for this search method. This generally does not need
 	 * to be populated for a server implementation, since servers will return
 	 * only one resource per class, but generally does need to be populated
-	 * for client implementations. 
+	 * for client implementations.
 	 */
 	// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
 	Class<? extends IResource> type() default IResource.class;
-	
+
+	/**
+	 * This method allows the return type for this method to be specified in a
+	 * non-type-specific way, using the text name of the resource, e.g. "Patient".
+	 * <p>
+	 * This attribute should be populate, or {@link #type()} should be, but not both.
+	 *
+	 * @since 5.4.0
+	 */
+	String typeName() default "";
+
 }
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Validate.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Validate.java
index 28b675544da..434700d90bc 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Validate.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Validate.java
@@ -52,7 +52,17 @@ public @interface Validate {
 	 */
 	// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
 	Class<? extends IBaseResource> type() default IBaseResource.class;
-	
+
+	/**
+	 * This method allows the return type for this method to be specified in a
+	 * non-type-specific way, using the text name of the resource, e.g. "Patient".
+	 *
+	 * This attribute should be populate, or {@link #type()} should be, but not both.
+	 *
+	 * @since 5.4.0
+	 */
+	String typeName() default "";
+
 	/**
 	 * Validation mode parameter annotation for the validation mode parameter (only supported
 	 * in FHIR DSTU2+). Parameter must be of type {@link ValidationModeEnum}.
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ExtensionUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ExtensionUtil.java
index 95b08d8ceb8..e14667a1af3 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ExtensionUtil.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ExtensionUtil.java
@@ -36,6 +36,13 @@ import java.util.stream.Collectors;
  */
 public class ExtensionUtil {
 
+	/**
+	 * Non instantiable
+	 */
+	private ExtensionUtil() {
+		// nothing
+	}
+
 	/**
 	 * Returns an extension with the specified URL creating one if it doesn't exist.
 	 *
@@ -46,7 +53,7 @@ public class ExtensionUtil {
 	 */
 	public static IBaseExtension<?, ?> getOrCreateExtension(IBase theBase, String theUrl) {
 		IBaseHasExtensions baseHasExtensions = validateExtensionSupport(theBase);
-		IBaseExtension extension = getExtensionByUrl(baseHasExtensions, theUrl);
+		IBaseExtension<?,?> extension = getExtensionByUrl(baseHasExtensions, theUrl);
 		if (extension == null) {
 			extension = baseHasExtensions.addExtension();
 			extension.setUrl(theUrl);
@@ -75,13 +82,27 @@ public class ExtensionUtil {
 	 */
 	public static IBaseExtension<?, ?> addExtension(IBase theBase, String theUrl) {
 		IBaseHasExtensions baseHasExtensions = validateExtensionSupport(theBase);
-		IBaseExtension extension = baseHasExtensions.addExtension();
+		IBaseExtension<?,?> extension = baseHasExtensions.addExtension();
 		if (theUrl != null) {
 			extension.setUrl(theUrl);
 		}
 		return extension;
 	}
 
+	/**
+	 * Adds an extension with the specified value
+	 *
+	 * @param theBase        The resource to update extension on
+	 * @param theUrl         Extension URL
+	 * @param theValueType   Type of the value to set in the extension
+	 * @param theValue       Extension value
+	 * @param theFhirContext The context containing FHIR resource definitions
+	 */
+	public static void addExtension(FhirContext theFhirContext, IBase theBase, String theUrl, String theValueType, Object theValue) {
+		IBaseExtension<?,?> ext = addExtension(theBase, theUrl);
+		setExtension(theFhirContext, ext, theValueType, theValue);
+	}
+
 	private static IBaseHasExtensions validateExtensionSupport(IBase theBase) {
 		if (!(theBase instanceof IBaseHasExtensions)) {
 			throw new IllegalArgumentException(String.format("Expected instance that supports extensions, but got %s", theBase));
@@ -118,7 +139,7 @@ public class ExtensionUtil {
 		if (!hasExtension(theBase, theExtensionUrl)) {
 			return false;
 		}
-		IBaseDatatype value = getExtensionByUrl((IBaseHasExtensions) theBase, theExtensionUrl).getValue();
+		IBaseDatatype value = getExtensionByUrl(theBase, theExtensionUrl).getValue();
 		if (value == null) {
 			return theExtensionValue == null;
 		}
@@ -133,7 +154,7 @@ public class ExtensionUtil {
 	 * @return Returns the first available extension with the specified URL, or null if such extension doesn't exist
 	 */
 	public static IBaseExtension<?, ?> getExtensionByUrl(IBase theBase, String theExtensionUrl) {
-		Predicate<IBaseExtension> filter;
+		Predicate<IBaseExtension<?,?>> filter;
 		if (theExtensionUrl == null) {
 			filter = (e -> true);
 		} else {
@@ -153,7 +174,7 @@ public class ExtensionUtil {
 	 * @param theFilter Predicate to match the extension against
 	 * @return Returns all extension with the specified URL, or an empty list if such extensions do not exist
 	 */
-	public static List<IBaseExtension<?, ?>> getExtensionsMatchingPredicate(IBase theBase, Predicate<? super IBaseExtension> theFilter) {
+	public static List<IBaseExtension<?, ?>> getExtensionsMatchingPredicate(IBase theBase, Predicate<? super IBaseExtension<?,?>> theFilter) {
 		return validateExtensionSupport(theBase)
 			.getExtension()
 			.stream()
@@ -189,7 +210,7 @@ public class ExtensionUtil {
 	 * @param theFilter Defines which extensions should be cleared
 	 * @return Returns all extension that were removed
 	 */
-	private static List<IBaseExtension<?, ?>> clearExtensionsMatchingPredicate(IBase theBase, Predicate<? super IBaseExtension> theFilter) {
+	private static List<IBaseExtension<?, ?>> clearExtensionsMatchingPredicate(IBase theBase, Predicate<? super IBaseExtension<?,?>> theFilter) {
 		List<IBaseExtension<?, ?>> retVal = getExtensionsMatchingPredicate(theBase, theFilter);
 		validateExtensionSupport(theBase)
 			.getExtension()
@@ -205,7 +226,7 @@ public class ExtensionUtil {
 	 * @return Returns all extension with the specified URL, or an empty list if such extensions do not exist
 	 */
 	public static List<IBaseExtension<?, ?>> getExtensionsByUrl(IBaseHasExtensions theBase, String theExtensionUrl) {
-		Predicate<IBaseExtension> urlEqualityPredicate = e -> theExtensionUrl.equals(e.getUrl());
+		Predicate<IBaseExtension<?,?>> urlEqualityPredicate = e -> theExtensionUrl.equals(e.getUrl());
 		return getExtensionsMatchingPredicate(theBase, urlEqualityPredicate);
 	}
 
@@ -216,8 +237,8 @@ public class ExtensionUtil {
 	 * @param theValue       The value to set
 	 * @param theFhirContext The context containing FHIR resource definitions
 	 */
-	public static void setExtension(FhirContext theFhirContext, IBaseExtension theExtension, String theValue) {
-		setExtension(theFhirContext, theExtension, "string", (Object) theValue);
+	public static void setExtension(FhirContext theFhirContext, IBaseExtension<?,?> theExtension, String theValue) {
+		setExtension(theFhirContext, theExtension, "string", theValue);
 	}
 
 	/**
@@ -228,7 +249,7 @@ public class ExtensionUtil {
 	 * @param theValue         The value to set
 	 * @param theFhirContext   The context containing FHIR resource definitions
 	 */
-	public static void setExtension(FhirContext theFhirContext, IBaseExtension theExtension, String theExtensionType, Object theValue) {
+	public static void setExtension(FhirContext theFhirContext, IBaseExtension<?,?> theExtension, String theExtensionType, Object theValue) {
 		theExtension.setValue(TerserUtil.newElement(theFhirContext, theExtensionType, theValue));
 	}
 
@@ -241,7 +262,7 @@ public class ExtensionUtil {
 	 * @param theFhirContext The context containing FHIR resource definitions
 	 */
 	public static void setExtensionAsString(FhirContext theFhirContext, IBase theBase, String theUrl, String theValue) {
-		IBaseExtension ext = getOrCreateExtension(theBase, theUrl);
+		IBaseExtension<?,?> ext = getOrCreateExtension(theBase, theUrl);
 		setExtension(theFhirContext, ext, theValue);
 	}
 
@@ -255,7 +276,7 @@ public class ExtensionUtil {
 	 * @param theFhirContext The context containing FHIR resource definitions
 	 */
 	public static void setExtension(FhirContext theFhirContext, IBase theBase, String theUrl, String theValueType, Object theValue) {
-		IBaseExtension ext = getOrCreateExtension(theBase, theUrl);
+		IBaseExtension<?,?> ext = getOrCreateExtension(theBase, theUrl);
 		setExtension(theFhirContext, ext, theValueType, theValue);
 	}
 
@@ -266,7 +287,7 @@ public class ExtensionUtil {
 	 * @param theRightExtension : Extension to be evaluated #2
 	 * @return Result of the comparison
 	 */
-	public static boolean equals(IBaseExtension theLeftExtension, IBaseExtension theRightExtension) {
+	public static boolean equals(IBaseExtension<?,?> theLeftExtension, IBaseExtension<?,?> theRightExtension) {
 		return TerserUtil.equals(theLeftExtension, theRightExtension);
 	}
 }
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/HapiExtensions.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/HapiExtensions.java
index f2cf7903407..6dfdca94d4b 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/HapiExtensions.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/HapiExtensions.java
@@ -116,7 +116,13 @@ public class HapiExtensions {
 	 */
     public static final String ASSOCIATED_GOLDEN_RESOURCE_EXTENSION_URL = "https://hapifhir.org/associated-patient-golden-resource/";
 
-    /**
+	/**
+	 * This extension provides an example value for a parameter value for
+	 * a REST operation (eg for an OperationDefinition)
+	 */
+	public static final String EXT_OP_PARAMETER_EXAMPLE_VALUE = "http://hapifhir.io/fhir/StructureDefinition/op-parameter-example-value";
+
+	/**
 	 * Non instantiable
 	 */
 	private HapiExtensions() {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java
index 40750ba0bef..5c873c94114 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java
@@ -25,6 +25,7 @@ import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
 import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
 import ca.uhn.fhir.context.FhirContext;
 import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.model.api.annotation.Description;
 import ca.uhn.fhir.model.primitive.StringDt;
 import org.apache.commons.lang3.Validate;
 import org.hl7.fhir.instance.model.api.IBase;
@@ -34,8 +35,13 @@ import org.hl7.fhir.instance.model.api.IBaseReference;
 import org.hl7.fhir.instance.model.api.IBaseResource;
 import org.hl7.fhir.instance.model.api.IPrimitiveType;
 
+import javax.annotation.Nullable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
@@ -43,6 +49,7 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
+import static org.apache.commons.lang3.StringUtils.isBlank;
 
 /**
  * Utilities for dealing with parameters resources in a version indepenedent way
@@ -418,4 +425,60 @@ public class ParametersUtil {
 			.findFirst();
 	}
 
+	@Nullable
+	public static String extractDescription(AnnotatedElement theType) {
+		Description description = theType.getAnnotation(Description.class);
+		if (description != null) {
+			return extractDescription(description);
+		} else {
+			return null;
+		}
+	}
+
+	@Nullable
+	public static String extractDescription(Description desc) {
+		String description = desc.value();
+		if (isBlank(description)) {
+			description = desc.formalDefinition();
+		}
+		if (isBlank(description)) {
+			description = desc.shortDefinition();
+		}
+		return defaultIfBlank(description, null);
+	}
+
+	@Nullable
+	public static String extractShortDefinition(AnnotatedElement theType) {
+		Description description = theType.getAnnotation(Description.class);
+		if (description != null) {
+			return defaultIfBlank(description.shortDefinition(), null);
+		} else {
+			return null;
+		}
+	}
+
+	public static String extractDescription(Annotation[] theParameterAnnotations) {
+		for (Annotation next : theParameterAnnotations) {
+			if (next instanceof Description) {
+				return extractDescription((Description)next);
+			}
+		}
+		return null;
+	}
+
+	public static List<String> extractExamples(Annotation[] theParameterAnnotations) {
+		ArrayList<String> retVal = null;
+		for (Annotation next : theParameterAnnotations) {
+			if (next instanceof Description) {
+				String[] examples = ((Description) next).example();
+				if (examples.length > 0) {
+					if (retVal == null) {
+						retVal = new ArrayList<>();
+					}
+					retVal.addAll(Arrays.asList(examples));
+				}
+			}
+		}
+		return retVal;
+	}
 }
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/SchemaBaseValidator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/SchemaBaseValidator.java
index c5b1b9832b3..f71d6e4c4a0 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/SchemaBaseValidator.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/SchemaBaseValidator.java
@@ -51,6 +51,7 @@ public class SchemaBaseValidator implements IValidatorModule {
 
 	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SchemaBaseValidator.class);
 	private static final Set<String> SCHEMA_NAMES;
+	private static boolean ourJaxp15Supported;
 
 	static {
 		HashSet<String> sn = new HashSet<>();
@@ -132,7 +133,9 @@ public class SchemaBaseValidator implements IValidatorModule {
 					 * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
 					 */
 					schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
+					ourJaxp15Supported = true;
 				} catch (SAXNotRecognizedException e) {
+					ourJaxp15Supported = false;
 					ourLog.warn("Jaxp 1.5 Support not found.", e);
 				}
 				schema = schemaFactory.newSchema(new Source[]{baseSource});
@@ -216,4 +219,8 @@ public class SchemaBaseValidator implements IValidatorModule {
 
 	}
 
+	public static boolean isJaxp15Supported() {
+		return ourJaxp15Supported;
+	}
+
 }
diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml
index fdb411e91dc..e069462596a 100644
--- a/hapi-fhir-bom/pom.xml
+++ b/hapi-fhir-bom/pom.xml
@@ -3,14 +3,14 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>ca.uhn.hapi.fhir</groupId>
     <artifactId>hapi-fhir-bom</artifactId>
-    <version>5.4.0-PRE7-SNAPSHOT</version>
+    <version>5.4.0-PRE8-SNAPSHOT</version>
     <packaging>pom</packaging>
 	 <name>HAPI FHIR BOM</name>
 
     <parent>
       <groupId>ca.uhn.hapi.fhir</groupId>
       <artifactId>hapi-deployable-pom</artifactId>
-      <version>5.4.0-PRE7-SNAPSHOT</version>
+      <version>5.4.0-PRE8-SNAPSHOT</version>
       <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
    </parent>
     
@@ -41,6 +41,11 @@
 				  <artifactId>hapi-fhir-server-mdm</artifactId>
 				  <version>${project.version}</version>
 			  </dependency>
+            <dependency>
+                <groupId>${project.groupId}</groupId>
+                <artifactId>hapi-fhir-server-openapi</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>${project.groupId}</groupId>
                 <artifactId>hapi-fhir-validation</artifactId>
diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml
index 92bafa40399..175508dea4a 100644
--- a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml
+++ b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml
index 0c0e9694712..ee4cd6101ed 100644
--- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml
+++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir-cli</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml
index 23fdc156c0e..6735fe66129 100644
--- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml
+++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../../hapi-deployable-pom</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml
index ae67b1dad2f..377ac7c1d20 100644
--- a/hapi-fhir-cli/pom.xml
+++ b/hapi-fhir-cli/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml
index 52fed03fb71..4656c910de6 100644
--- a/hapi-fhir-client-okhttp/pom.xml
+++ b/hapi-fhir-client-okhttp/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml
index f407f56be2d..d86715e8679 100644
--- a/hapi-fhir-client/pom.xml
+++ b/hapi-fhir-client/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/OperationMethodBinding.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/OperationMethodBinding.java
index 9e5f4c100da..eb17cbb2241 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/OperationMethodBinding.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/OperationMethodBinding.java
@@ -30,6 +30,7 @@ import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation;
 import ca.uhn.fhir.rest.param.ParameterUtil;
 import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
 import ca.uhn.fhir.util.FhirTerser;
+import ca.uhn.fhir.util.ParametersUtil;
 import org.hl7.fhir.instance.model.api.*;
 
 import java.lang.reflect.Method;
@@ -64,10 +65,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
 
 		Description description = theMethod.getAnnotation(Description.class);
 		if (description != null) {
-			myDescription = description.formalDefinition();
-			if (isBlank(myDescription)) {
-				myDescription = description.shortDefinition();
-			}
+			myDescription = ParametersUtil.extractDescription(description);
 		}
 		if (isBlank(myDescription)) {
 			myDescription = null;
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/SearchMethodBinding.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/SearchMethodBinding.java
index 1e19ab8abe5..7341e3f6fa4 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/SearchMethodBinding.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/method/SearchMethodBinding.java
@@ -33,6 +33,7 @@ import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation;
 import ca.uhn.fhir.rest.param.ParameterUtil;
 import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
 import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.util.ParametersUtil;
 import org.apache.commons.lang3.StringUtils;
 import org.hl7.fhir.instance.model.api.IBaseResource;
 import org.hl7.fhir.instance.model.api.IIdType;
@@ -59,15 +60,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
 		this.myQueryName = StringUtils.defaultIfBlank(search.queryName(), null);
 		this.myCompartmentName = StringUtils.defaultIfBlank(search.compartmentName(), null);
 		this.myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext());
-
-		Description desc = theMethod.getAnnotation(Description.class);
-		if (desc != null) {
-			if (isNotBlank(desc.formalDefinition())) {
-				myDescription = StringUtils.defaultIfBlank(desc.formalDefinition(), null);
-			} else {
-				myDescription = StringUtils.defaultIfBlank(desc.shortDefinition(), null);
-			}
-		}
+		this.myDescription = ParametersUtil.extractDescription(theMethod);
 
 		/*
 		 * Check for parameter combinations and names that are invalid
diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml
index a988e2f45c5..7778e520ac6 100644
--- a/hapi-fhir-converter/pom.xml
+++ b/hapi-fhir-converter/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml
index 8ea8d57f03b..95e10af58f2 100644
--- a/hapi-fhir-dist/pom.xml
+++ b/hapi-fhir-dist/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-docs/pom.xml b/hapi-fhir-docs/pom.xml
index 2fcd0c94674..d15eda3ec2e 100644
--- a/hapi-fhir-docs/pom.xml
+++ b/hapi-fhir-docs/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
@@ -55,6 +55,11 @@
 			<artifactId>hapi-fhir-jpaserver-base</artifactId>
 			<version>${project.version}</version>
 		</dependency>
+		<dependency>
+			<groupId>ca.uhn.hapi.fhir</groupId>
+			<artifactId>hapi-fhir-server-openapi</artifactId>
+			<version>${project.version}</version>
+		</dependency>
 
 		<dependency>
 			<groupId>com.fasterxml.jackson.dataformat</groupId>
@@ -78,13 +83,13 @@
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-structures-dstu2</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 			<scope>compile</scope>
 		</dependency>
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-jpaserver-subscription</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 			<scope>compile</scope>
 		</dependency>
 		<dependency>
@@ -101,7 +106,7 @@
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-testpage-overlay</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 			<classifier>classes</classifier>
 		</dependency>
 		<dependency>
@@ -118,7 +123,7 @@
 			<groupId>com.fasterxml.jackson.core</groupId>
 			<artifactId>jackson-databind</artifactId>
 		</dependency>
-	</dependencies>
+    </dependencies>
 
 	<build>
 		<plugins>
diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/CreateCompositionAndGenerateDocument.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/CreateCompositionAndGenerateDocument.java
new file mode 100644
index 00000000000..546bf051f39
--- /dev/null
+++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/CreateCompositionAndGenerateDocument.java
@@ -0,0 +1,73 @@
+package ca.uhn.hapi.fhir.docs;
+
+/*-
+ * #%L
+ * HAPI FHIR - Docs
+ * %%
+ * Copyright (C) 2014 - 2021 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.rest.client.api.IGenericClient;
+import org.hl7.fhir.r4.model.Bundle;
+import org.hl7.fhir.r4.model.Composition;
+import org.hl7.fhir.r4.model.Observation;
+import org.hl7.fhir.r4.model.Parameters;
+import org.hl7.fhir.r4.model.Patient;
+import org.hl7.fhir.r4.model.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CreateCompositionAndGenerateDocument {
+
+  private static final Logger ourLog = LoggerFactory.getLogger(CreateCompositionAndGenerateDocument.class);
+
+  public static void main(String[] args) {
+
+    // START SNIPPET: CreateCompositionAndGenerateDocument
+    FhirContext ctx = FhirContext.forR4();
+    IGenericClient client = ctx.newRestfulGenericClient("http://hapi.fhir.org/baseR4");
+
+    Patient patient = new Patient();
+    patient.setId("PATIENT-ABC");
+    patient.setActive(true);
+    client.update().resource(patient).execute();
+
+    Observation observation = new Observation();
+    observation.setId("OBSERVATION-ABC");
+    observation.setSubject(new Reference("Patient/PATIENT-ABC"));
+    observation.setStatus(Observation.ObservationStatus.FINAL);
+    client.update().resource(observation).execute();
+
+    Composition composition = new Composition();
+    composition.setId("COMPOSITION-ABC");
+    composition.setSubject(new Reference("Patient/PATIENT-ABC"));
+    composition.addSection().setFocus(new Reference("Observation/OBSERVATION-ABC"));
+    client.update().resource(composition).execute();
+
+    Bundle document = client
+       .operation()
+       .onInstance("Composition/COMPOSITION-ABC")
+       .named("$document")
+       .withNoParameters(Parameters.class)
+       .returnResourceType(Bundle.class)
+       .execute();
+
+    ourLog.info("Document bundle: {}", ctx.newJsonParser().setPrettyPrint(true).encodeResourceToString(document));
+    // END SNIPPET: CreateCompositionAndGenerateDocument
+
+  }
+}
diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ServletExamples.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ServletExamples.java
index e682175e0fb..9ed097ad9ed 100644
--- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ServletExamples.java
+++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ServletExamples.java
@@ -23,6 +23,7 @@ package ca.uhn.hapi.fhir.docs;
 import ca.uhn.fhir.context.FhirContext;
 import ca.uhn.fhir.context.support.IValidationSupport;
 import ca.uhn.fhir.rest.api.PreferHandlingEnum;
+import ca.uhn.fhir.rest.openapi.OpenApiInterceptor;
 import ca.uhn.fhir.rest.server.RestfulServer;
 import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
 import ca.uhn.fhir.rest.server.interceptor.*;
@@ -65,6 +66,24 @@ public class ServletExamples {
    }
    // END SNIPPET: loggingInterceptor
 
+   // START SNIPPET: OpenApiInterceptor
+	@WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
+   public class RestfulServerWithOpenApi extends RestfulServer {
+
+      @Override
+      protected void initialize() throws ServletException {
+
+         // ... define your resource providers here ...
+
+         // Now register the interceptor
+			OpenApiInterceptor openApiInterceptor = new OpenApiInterceptor();
+         registerInterceptor(openApiInterceptor);
+
+      }
+
+   }
+   // END SNIPPET: OpenApiInterceptor
+
    // START SNIPPET: validatingInterceptor
    @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
    public class ValidatingServerWithLogging extends RestfulServer {
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2560-openapi-support.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2560-openapi-support.yaml
new file mode 100644
index 00000000000..cfd9b53dc35
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2560-openapi-support.yaml
@@ -0,0 +1,5 @@
+---
+type: add
+issue: 2560
+title: "A new interceptor called `OpenApiInterceptor` has been added. This interceptor can be registered against FHIR Servers to
+   automatically add support for OpenAPI / Swagger."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2571-add-delete-to-bundle-builder.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2571-add-delete-to-bundle-builder.yaml
new file mode 100644
index 00000000000..6da48fc802c
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2571-add-delete-to-bundle-builder.yaml
@@ -0,0 +1,4 @@
+---
+type: fix
+issue: 2571
+title: "Added support for deleting resources to BundleBuilder via method `addTransactionDeleteEntry`."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/changes.yaml
index 928093b5dd3..c9053c1890b 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/changes.yaml
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/changes.yaml
@@ -11,6 +11,7 @@
 <li>Guava (Core): 30.1-jre -> 30.1.1-jre</li>
 <li>Jackson (Core): 2.12.1 -> 2.12.3</li>
 <li>Woodstox (Core): 6.2.3 -> 6.2.5</li>
+<li>Apache Jena (Core/RDF): 3.16.0 -> 3.17.0</li>
 <li>Gson (JPA): 2.8.5 -> 2.8.6</li>
 <li>Caffeine (JPA): 2.7.0 -> 3.0.1</li>
 <li>Hibernate (JPA): 5.4.26.Final -> 5.4.30.Final</li>
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md
index e3267ffed28..9a6c405390d 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md
@@ -161,3 +161,13 @@ This following example shows how to load all pages of a bundle by fetching each
 ```java
 {{snippet:classpath:/ca/uhn/hapi/fhir/docs/BundleFetcher.java|loadAll}}
 ```
+
+# Create Composition and Generate Document
+
+This example shows how to generate a Composition resource with two linked resources, then apply the server `$document` operation to generate a document based on this composition.
+
+```java
+{{snippet:classpath:/ca/uhn/hapi/fhir/docs/CreateCompositionAndGenerateDocument.java|CreateCompositionAndGenerateDocument}}
+```
+
+
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties
index 580cda79968..dac40d5f791 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties
@@ -47,6 +47,7 @@ page.server_plain.web_testpage_overlay=Web Testpage Overlay
 page.server_plain.multitenancy=Multitenancy
 page.server_plain.jax_rs=JAX-RS Support
 page.server_plain.customizing_the_capabilitystatement=Customizing the CapabilityStatement
+page.server_plain.openapi=OpenAPI / Swagger
 
 section.server_jpa.title=JPA Server
 page.server_jpa.introduction=Introduction
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
index f21e80292ec..2f6ecef421d 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
@@ -196,6 +196,11 @@ Some security audit tools require that servers return an HTTP 405 if an unsuppor
 * [BanUnsupportedHttpMethodsInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/BanUnsupportedHttpMethodsInterceptor.html)
 * [BanUnsupportedHttpMethodsInterceptor Source](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/BanUnsupportedHttpMethodsInterceptor.java)
 
+# Server: OpenAPI / Swagger Support
+
+An interceptor can be registered against your server that enables support for OpenAPI (aka Swagger) automatically. See [OpenAPI](/docs/server_plain/openapi.html) for more information.
+
+
 # Subscription: Subscription Debug Log Interceptor
 
 When using Subscriptions, the debug log interceptor can be used to add a number of additional lines to the server logs showing the internals of the subscription processing pipeline.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/openapi.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/openapi.md
new file mode 100644
index 00000000000..4f8a7f040ff
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/openapi.md
@@ -0,0 +1,39 @@
+# OpenAPI / Swagger Support
+
+In HAPI FHIR, support for OpenAPI (aka Swagger) is supported via the [OpenApiInterceptor](/hapi-fhir/apidocs/hapi-fhir-server-openapi/ca/uhn/fhir/rest/openapi/OpenApiInterceptor.html).
+
+Note that this interceptor supports servers using the RestfulServer (aka HAPI FHIR Plain Server and JPA Server), and does not currently support JAX-RS servers. 
+
+When this interceptor is registered against the server, it performs the following 3 tasks:
+
+### System Functionality
+
+* OpenAPI 3.0 Documentation will be served at `[baseUrl]/api-docs`. This documentation is generated by the interceptor using information from the server's CapabilityStatement as well as from its automatically generated OperationDefinitions.  
+
+### User Functionality
+
+* Anytime a user using a browser navigates to the Base URL of the server, they will be automatically redirected to `[baseUrl]/swagger-ui/`
+
+* A customized version of the [Swagger UI](https://swagger.io/tools/swagger-ui/) tool will be served at `[baseUrl]/swagger-ui/`
+
+# Enabling OpenAPI
+
+The HAPI FHIR OpenAPI functionality is supplied in a dedicated module called `hapi-fhir-server-openapi`. To enable this functionality you must first include this module in your project. For example, Maven users should include the following dependency:
+
+```xml
+<dependency>
+	<groupId>ca.uhn.hapi.fhir</groupId>
+	<artifactId>hapi-fhir-server-openapi</artifactId>
+	<version>VERSION</version>
+</dependency>
+```
+
+You then simply have to register the interceptor against your RestfulServer instance.
+
+```java
+{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ServletExamples.java|OpenApiInterceptor}}
+```
+
+# Demonstration
+
+See the HAPI FHIR Test Server for a demonstration of HAPI FHIR OpenAPI functionality: http://hapi.fhir.org/baseR4/swagger-ui/
diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml
index 1d22f0b5e4a..6af26480e26 100644
--- a/hapi-fhir-jacoco/pom.xml
+++ b/hapi-fhir-jacoco/pom.xml
@@ -11,7 +11,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
@@ -36,6 +36,11 @@
 			<artifactId>hapi-fhir-server-mdm</artifactId>
 			<version>${project.version}</version>
 		</dependency>
+        <dependency>
+            <groupId>ca.uhn.hapi.fhir</groupId>
+            <artifactId>hapi-fhir-server-openapi</artifactId>
+            <version>${project.version}</version>
+        </dependency>
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-client</artifactId>
diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml
index b2004c3ad14..697e4479704 100644
--- a/hapi-fhir-jaxrsserver-base/pom.xml
+++ b/hapi-fhir-jaxrsserver-base/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-jaxrsserver-example/pom.xml b/hapi-fhir-jaxrsserver-example/pom.xml
index c3417a5963f..6626a7653dc 100644
--- a/hapi-fhir-jaxrsserver-example/pom.xml
+++ b/hapi-fhir-jaxrsserver-example/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-jpaserver-api/pom.xml b/hapi-fhir-jpaserver-api/pom.xml
index fa591624b35..35aecfe7055 100644
--- a/hapi-fhir-jpaserver-api/pom.xml
+++ b/hapi-fhir-jpaserver-api/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml
index 3fed26d92f7..357583621aa 100644
--- a/hapi-fhir-jpaserver-base/pom.xml
+++ b/hapi-fhir-jpaserver-base/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
@@ -577,7 +577,14 @@
 			<scope>test</scope>
 		</dependency>
 
-		<dependency>
+        <dependency>
+            <groupId>ca.uhn.hapi.fhir</groupId>
+            <artifactId>hapi-fhir-server-openapi</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
 			<groupId>com.github.ben-manes.caffeine</groupId>
 			<artifactId>caffeine</artifactId>
 		</dependency>
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java
index 2b4cf56a725..4f772f60abf 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java
@@ -8,10 +8,10 @@ import ca.uhn.fhir.jpa.model.util.JpaConstants;
 import ca.uhn.fhir.rest.param.DateRangeParam;
 import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
 import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
+import ca.uhn.fhir.util.ParametersUtil;
 import org.apache.commons.lang3.StringUtils;
+import org.hl7.fhir.instance.model.api.IBaseParameters;
 import org.hl7.fhir.instance.model.api.IPrimitiveType;
-import org.hl7.fhir.r4.model.IntegerType;
-import org.hl7.fhir.r4.model.Parameters;
 import org.jboss.logging.MDC;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -78,13 +78,11 @@ public class BaseJpaProvider {
 		return options;
 	}
 
-	protected Parameters createExpungeResponse(ExpungeOutcome theOutcome) {
-		Parameters retVal = new Parameters();
-		retVal
-			.addParameter()
-			.setName(JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT)
-			.setValue(new IntegerType(theOutcome.getDeletedCount()));
-		return retVal;
+	protected IBaseParameters createExpungeResponse(ExpungeOutcome theOutcome) {
+		IBaseParameters parameters = ParametersUtil.newInstance(getContext());
+		String value = Integer.toString(theOutcome.getDeletedCount());
+		ParametersUtil.addParameterToParameters(getContext(), parameters, JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, "integer", value);
+		return parameters;
 	}
 
 	/**
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProvider.java
index 3d73fd326d8..307625c456a 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProvider.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProvider.java
@@ -24,30 +24,47 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
 import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
 import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
 import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
+import ca.uhn.fhir.jpa.model.util.JpaConstants;
+import ca.uhn.fhir.model.api.annotation.Description;
 import ca.uhn.fhir.rest.annotation.At;
 import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
+import ca.uhn.fhir.rest.annotation.Create;
+import ca.uhn.fhir.rest.annotation.Delete;
 import ca.uhn.fhir.rest.annotation.History;
 import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Operation;
+import ca.uhn.fhir.rest.annotation.OperationParam;
 import ca.uhn.fhir.rest.annotation.Patch;
 import ca.uhn.fhir.rest.annotation.Read;
 import ca.uhn.fhir.rest.annotation.ResourceParam;
 import ca.uhn.fhir.rest.annotation.Since;
+import ca.uhn.fhir.rest.annotation.Update;
+import ca.uhn.fhir.rest.annotation.Validate;
+import ca.uhn.fhir.rest.api.EncodingEnum;
+import ca.uhn.fhir.rest.api.MethodOutcome;
 import ca.uhn.fhir.rest.api.PatchTypeEnum;
+import ca.uhn.fhir.rest.api.ValidationModeEnum;
 import ca.uhn.fhir.rest.api.server.IBundleProvider;
 import ca.uhn.fhir.rest.api.server.RequestDetails;
 import ca.uhn.fhir.rest.param.DateRangeParam;
 import ca.uhn.fhir.rest.server.IResourceProvider;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
 import ca.uhn.fhir.util.CoverageIgnore;
+import ca.uhn.fhir.util.ParametersUtil;
+import org.hl7.fhir.instance.model.api.IBaseMetaType;
 import org.hl7.fhir.instance.model.api.IBaseParameters;
 import org.hl7.fhir.instance.model.api.IBaseResource;
 import org.hl7.fhir.instance.model.api.IIdType;
 import org.hl7.fhir.instance.model.api.IPrimitiveType;
-import org.hl7.fhir.r4.model.Parameters;
 import org.springframework.beans.factory.annotation.Required;
 
 import javax.servlet.http.HttpServletRequest;
 import java.util.Date;
 
+import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META;
+import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META_ADD;
+import static ca.uhn.fhir.jpa.model.util.JpaConstants.OPERATION_META_DELETE;
+
 public abstract class BaseJpaResourceProvider<T extends IBaseResource> extends BaseJpaProvider implements IResourceProvider {
 
 	private IFhirResourceDao<T> myDao;
@@ -62,7 +79,7 @@ public abstract class BaseJpaResourceProvider<T extends IBaseResource> extends B
 	}
 
 
-	protected Parameters doExpunge(IIdType theIdParam, IPrimitiveType<? extends Integer> theLimit, IPrimitiveType<? extends Boolean> theExpungeDeletedResources, IPrimitiveType<? extends Boolean> theExpungeOldVersions, IPrimitiveType<? extends Boolean> theExpungeEverything, RequestDetails theRequest) {
+	protected IBaseParameters doExpunge(IIdType theIdParam, IPrimitiveType<? extends Integer> theLimit, IPrimitiveType<? extends Boolean> theExpungeDeletedResources, IPrimitiveType<? extends Boolean> theExpungeOldVersions, IPrimitiveType<? extends Boolean> theExpungeEverything, RequestDetails theRequest) {
 
 		ExpungeOptions options = createExpungeOptions(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything);
 
@@ -143,4 +160,134 @@ public abstract class BaseJpaResourceProvider<T extends IBaseResource> extends B
 		}
 	}
 
+	@Create
+	public MethodOutcome create(HttpServletRequest theRequest, @ResourceParam T theResource, @ConditionalUrlParam String theConditional, RequestDetails theRequestDetails) {
+		startRequest(theRequest);
+		try {
+			if (theConditional != null) {
+				return getDao().create(theResource, theConditional, theRequestDetails);
+			} else {
+				return getDao().create(theResource, theRequestDetails);
+			}
+		} finally {
+			endRequest(theRequest);
+		}
+	}
+
+	@Delete()
+	public MethodOutcome delete(HttpServletRequest theRequest, @IdParam IIdType theResource, @ConditionalUrlParam(supportsMultiple = true) String theConditional, RequestDetails theRequestDetails) {
+		startRequest(theRequest);
+		try {
+			if (theConditional != null) {
+				return getDao().deleteByUrl(theConditional, theRequestDetails);
+			} else {
+				return getDao().delete(theResource, theRequestDetails);
+			}
+		} finally {
+			endRequest(theRequest);
+		}
+	}
+
+	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
+		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, typeName = "integer")
+	})
+	public IBaseParameters expunge(
+		@IdParam IIdType theIdParam,
+		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT, typeName = "integer") IPrimitiveType<Integer> theLimit,
+		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES, typeName = "boolean") IPrimitiveType<Boolean> theExpungeDeletedResources,
+		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS, typeName = "boolean") IPrimitiveType<Boolean> theExpungeOldVersions,
+		RequestDetails theRequest) {
+		return doExpunge(theIdParam, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null, theRequest);
+	}
+
+	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
+		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, typeName = "integer")
+	})
+	public IBaseParameters expunge(
+		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT, typeName = "integer") IPrimitiveType<Integer> theLimit,
+		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES, typeName = "boolean") IPrimitiveType<Boolean> theExpungeDeletedResources,
+		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS, typeName = "boolean") IPrimitiveType<Boolean> theExpungeOldVersions,
+		RequestDetails theRequest) {
+		return doExpunge(null, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null, theRequest);
+	}
+
+	@Description("Request a global list of tags, profiles, and security labels")
+	@Operation(name = OPERATION_META, idempotent = true, returnParameters = {
+		@OperationParam(name = "return", typeName = "Meta")
+	})
+	public IBaseParameters meta(RequestDetails theRequestDetails) {
+		Class metaType = getContext().getElementDefinition("Meta").getImplementingClass();
+		IBaseMetaType metaGetOperation = getDao().metaGetOperation(metaType, theRequestDetails);
+		IBaseParameters parameters = ParametersUtil.newInstance(getContext());
+		ParametersUtil.addParameterToParameters(getContext(), parameters, "return", metaGetOperation);
+		return parameters;
+	}
+
+	@Description("Request a list of tags, profiles, and security labels for a specfic resource instance")
+	@Operation(name = OPERATION_META, idempotent = true, returnParameters = {
+		@OperationParam(name = "return", typeName = "Meta")
+	})
+	public IBaseParameters meta(@IdParam IIdType theId, RequestDetails theRequestDetails) {
+		Class metaType = getContext().getElementDefinition("Meta").getImplementingClass();
+		IBaseMetaType metaGetOperation = getDao().metaGetOperation(metaType, theId, theRequestDetails);
+
+		IBaseParameters parameters = ParametersUtil.newInstance(getContext());
+		ParametersUtil.addParameterToParameters(getContext(), parameters, "return", metaGetOperation);
+		return parameters;
+	}
+
+	@Description("Add tags, profiles, and/or security labels to a resource")
+	@Operation(name = OPERATION_META_ADD, idempotent = false, returnParameters = {
+		@OperationParam(name = "return", typeName = "Meta")
+	})
+	public IBaseParameters metaAdd(@IdParam IIdType theId, @OperationParam(name = "meta", typeName = "Meta") IBaseMetaType theMeta, RequestDetails theRequestDetails) {
+		if (theMeta == null) {
+			throw new InvalidRequestException("Input contains no parameter with name 'meta'");
+		}
+		IBaseMetaType metaAddOperation = getDao().metaAddOperation(theId, theMeta, theRequestDetails);
+		IBaseParameters parameters = ParametersUtil.newInstance(getContext());
+		ParametersUtil.addParameterToParameters(getContext(), parameters, "return", metaAddOperation);
+		return parameters;
+	}
+
+	@Description("Delete tags, profiles, and/or security labels from a resource")
+	@Operation(name = OPERATION_META_DELETE, idempotent = false, returnParameters = {
+		@OperationParam(name = "return", typeName = "Meta")
+	})
+	public IBaseParameters metaDelete(@IdParam IIdType theId, @OperationParam(name = "meta", typeName = "Meta") IBaseMetaType theMeta, RequestDetails theRequestDetails) {
+		if (theMeta == null) {
+			throw new InvalidRequestException("Input contains no parameter with name 'meta'");
+		}
+		IBaseMetaType metaDelete = getDao().metaDeleteOperation(theId, theMeta, theRequestDetails);
+		IBaseParameters parameters = ParametersUtil.newInstance(getContext());
+		ParametersUtil.addParameterToParameters(getContext(), parameters, "return", metaDelete);
+		return parameters;
+	}
+
+	@Update
+	public MethodOutcome update(HttpServletRequest theRequest, @ResourceParam T theResource, @IdParam IIdType theId, @ConditionalUrlParam String theConditional, RequestDetails theRequestDetails) {
+		startRequest(theRequest);
+		try {
+			if (theConditional != null) {
+				return getDao().update(theResource, theConditional, theRequestDetails);
+			} else {
+				return getDao().update(theResource, theRequestDetails);
+			}
+		} finally {
+			endRequest(theRequest);
+		}
+	}
+
+	@Validate
+	public MethodOutcome validate(@ResourceParam T theResource, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
+											@Validate.Profile String theProfile, RequestDetails theRequestDetails) {
+		return validate(theResource, null, theRawResource, theEncoding, theMode, theProfile, theRequestDetails);
+	}
+
+	@Validate
+	public MethodOutcome validate(@ResourceParam T theResource, @IdParam IIdType theId, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
+											@Validate.Profile String theProfile, RequestDetails theRequestDetails) {
+		return getDao().validate(theResource, theId, theRawResource, theEncoding, theMode, theProfile, theRequestDetails);
+	}
+
 }
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProvider.java
index 4bd29532d1f..84751fb6c96 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProvider.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProvider.java
@@ -23,15 +23,18 @@ package ca.uhn.fhir.jpa.provider;
 import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
 import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
 import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
+import ca.uhn.fhir.jpa.model.util.JpaConstants;
 import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
 import ca.uhn.fhir.rest.annotation.At;
 import ca.uhn.fhir.rest.annotation.History;
+import ca.uhn.fhir.rest.annotation.Operation;
+import ca.uhn.fhir.rest.annotation.OperationParam;
 import ca.uhn.fhir.rest.annotation.Since;
 import ca.uhn.fhir.rest.api.server.IBundleProvider;
 import ca.uhn.fhir.rest.api.server.RequestDetails;
 import ca.uhn.fhir.rest.param.DateRangeParam;
+import org.hl7.fhir.instance.model.api.IBaseParameters;
 import org.hl7.fhir.instance.model.api.IPrimitiveType;
-import org.hl7.fhir.r4.model.Parameters;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Required;
 
@@ -55,7 +58,22 @@ public class BaseJpaSystemProvider<T, MT> extends BaseJpaProvider implements IJp
 		return myResourceReindexingSvc;
 	}
 
-	protected Parameters doExpunge(IPrimitiveType<? extends Integer> theLimit, IPrimitiveType<? extends Boolean> theExpungeDeletedResources, IPrimitiveType<? extends Boolean> theExpungeOldVersions, IPrimitiveType<? extends Boolean> theExpungeEverything, RequestDetails theRequestDetails) {
+	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
+		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, typeName = "integer")
+	})
+	public IBaseParameters expunge(
+		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT, typeName = "integer") IPrimitiveType<Integer> theLimit,
+		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES, typeName = "boolean") IPrimitiveType<Boolean> theExpungeDeletedResources,
+		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS, typeName = "boolean") IPrimitiveType<Boolean> theExpungeOldVersions,
+		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING, typeName = "boolean") IPrimitiveType<Boolean> theExpungeEverything,
+		RequestDetails theRequestDetails
+	) {
+		ExpungeOptions options = createExpungeOptions(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything);
+		ExpungeOutcome outcome = getDao().expunge(options, theRequestDetails);
+		return createExpungeResponse(outcome);
+	}
+
+	protected IBaseParameters doExpunge(IPrimitiveType<? extends Integer> theLimit, IPrimitiveType<? extends Boolean> theExpungeDeletedResources, IPrimitiveType<? extends Boolean> theExpungeOldVersions, IPrimitiveType<? extends Boolean> theExpungeEverything, RequestDetails theRequestDetails) {
 		ExpungeOptions options = createExpungeOptions(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything);
 		ExpungeOutcome outcome = getDao().expunge(options, theRequestDetails);
 		return createExpungeResponse(outcome);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProviderDstu2Plus.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProviderDstu2Plus.java
index a1a3f68f303..9c3b95007d8 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProviderDstu2Plus.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaSystemProviderDstu2Plus.java
@@ -20,19 +20,27 @@ package ca.uhn.fhir.jpa.provider;
  * #L%
  */
 
+import ca.uhn.fhir.jpa.model.util.JpaConstants;
+import ca.uhn.fhir.model.api.annotation.Description;
 import ca.uhn.fhir.rest.annotation.Operation;
 import ca.uhn.fhir.rest.annotation.OperationParam;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
 import ca.uhn.fhir.util.ParametersUtil;
+import org.hl7.fhir.dstu3.model.Bundle;
+import org.hl7.fhir.instance.model.api.IBaseBundle;
 import org.hl7.fhir.instance.model.api.IBaseParameters;
 import org.hl7.fhir.instance.model.api.IBaseResource;
 import org.hl7.fhir.instance.model.api.IPrimitiveType;
 
+import javax.servlet.http.HttpServletRequest;
+
 import static org.apache.commons.lang3.StringUtils.isNotBlank;
 
 public abstract class BaseJpaSystemProviderDstu2Plus<T, MT> extends BaseJpaSystemProvider<T, MT> {
 
 
-	@Operation(name = MARK_ALL_RESOURCES_FOR_REINDEXING, idempotent = true, returnParameters = {
+	@Description("Marks all currently existing resources of a given type, or all resources of all types, for reindexing.")
+	@Operation(name = MARK_ALL_RESOURCES_FOR_REINDEXING, idempotent = false, returnParameters = {
 		@OperationParam(name = "status")
 	})
 	public IBaseResource markAllResourcesForReindexing(
@@ -53,7 +61,8 @@ public abstract class BaseJpaSystemProviderDstu2Plus<T, MT> extends BaseJpaSyste
 		return retVal;
 	}
 
-	@Operation(name = PERFORM_REINDEXING_PASS, idempotent = true, returnParameters = {
+	@Description("Forces a single pass of the resource reindexing processor")
+	@Operation(name = PERFORM_REINDEXING_PASS, idempotent = false, returnParameters = {
 		@OperationParam(name = "status")
 	})
 	public IBaseResource performReindexingPass() {
@@ -72,4 +81,28 @@ public abstract class BaseJpaSystemProviderDstu2Plus<T, MT> extends BaseJpaSyste
 		return retVal;
 	}
 
+	/**
+	 * $process-message
+	 */
+	@Description("Accept a FHIR Message Bundle for processing")
+	@Operation(name = JpaConstants.OPERATION_PROCESS_MESSAGE, idempotent = false)
+	public IBaseBundle processMessage(
+		HttpServletRequest theServletRequest,
+		RequestDetails theRequestDetails,
+
+		@OperationParam(name = "content", min = 1, max = 1, typeName = "Bundle")
+		@Description(formalDefinition = "The message to process (or, if using asynchronous messaging, it may be a response message to accept)")
+			IBaseBundle theMessageToProcess
+	) {
+
+		startRequest(theServletRequest);
+		try {
+			return getDao().processMessage(theRequestDetails, theMessageToProcess);
+		} finally {
+			endRequest(theServletRequest);
+		}
+
+	}
+
+
 }
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/DiffProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/DiffProvider.java
index cc2f98a7797..2f82366aca9 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/DiffProvider.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/DiffProvider.java
@@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext;
 import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
 import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
 import ca.uhn.fhir.jpa.patch.FhirPatch;
+import ca.uhn.fhir.model.api.annotation.Description;
 import ca.uhn.fhir.rest.annotation.IdParam;
 import ca.uhn.fhir.rest.annotation.Operation;
 import ca.uhn.fhir.rest.annotation.OperationParam;
@@ -49,14 +50,23 @@ public class DiffProvider {
 	@Autowired
 	private DaoRegistry myDaoRegistry;
 
+	@Description(
+		value="This operation examines two resource versions (can be two versions of the same resource, or two different resources) and generates a FHIR Patch document showing the differences.",
+		shortDefinition = "Comparte two resources or two versions of a single resource")
 	@Operation(name = ProviderConstants.DIFF_OPERATION_NAME, global = true, idempotent = true)
 	public IBaseParameters diff(
 		@IdParam IIdType theResourceId,
-		@OperationParam(name = ProviderConstants.DIFF_FROM_VERSION_PARAMETER, typeName = "string", min = 0, max = 1) IPrimitiveType<?> theFromVersion,
-		@OperationParam(name = ProviderConstants.DIFF_INCLUDE_META_PARAMETER, typeName = "boolean", min = 0, max = 1) IPrimitiveType<Boolean> theIncludeMeta,
+
+		@Description(value = "The resource ID and version to diff from", example = "Patient/example/version/1")
+		@OperationParam(name = ProviderConstants.DIFF_FROM_VERSION_PARAMETER, typeName = "string", min = 0, max = 1)
+			IPrimitiveType<?> theFromVersion,
+
+		@Description(value = "Should differences in the Resource.meta element be included in the diff", example = "false")
+		@OperationParam(name = ProviderConstants.DIFF_INCLUDE_META_PARAMETER, typeName = "boolean", min = 0, max = 1)
+			IPrimitiveType<Boolean> theIncludeMeta,
 		RequestDetails theRequestDetails) {
 
-		IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResourceId.getResourceType());
+		IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(theResourceId.getResourceType());
 		IBaseResource targetResource = dao.read(theResourceId, theRequestDetails);
 		IBaseResource sourceResource = null;
 
@@ -82,15 +92,23 @@ public class DiffProvider {
 		}
 
 		FhirPatch fhirPatch = newPatch(theIncludeMeta);
-		IBaseParameters diff = fhirPatch.diff(sourceResource, targetResource);
-		return diff;
+		return fhirPatch.diff(sourceResource, targetResource);
 	}
 
+	@Description("This operation examines two resource versions (can be two versions of the same resource, or two different resources) and generates a FHIR Patch document showing the differences.")
 	@Operation(name = ProviderConstants.DIFF_OPERATION_NAME, idempotent = true)
 	public IBaseParameters diff(
-		@OperationParam(name = ProviderConstants.DIFF_FROM_PARAMETER, typeName = "id", min = 1, max = 1) IIdType theFromVersion,
-		@OperationParam(name = ProviderConstants.DIFF_TO_PARAMETER, typeName = "id", min = 1, max = 1) IIdType theToVersion,
-		@OperationParam(name = ProviderConstants.DIFF_INCLUDE_META_PARAMETER, typeName = "boolean", min = 0, max = 1) IPrimitiveType<Boolean> theIncludeMeta,
+		@Description(value = "The resource ID and version to diff from", example = "Patient/example/version/1")
+		@OperationParam(name = ProviderConstants.DIFF_FROM_PARAMETER, typeName = "id", min = 1, max = 1)
+			IIdType theFromVersion,
+
+		@Description(value = "The resource ID and version to diff to", example = "Patient/example/version/2")
+		@OperationParam(name = ProviderConstants.DIFF_TO_PARAMETER, typeName = "id", min = 1, max = 1)
+			IIdType theToVersion,
+
+		@Description(value = "Should differences in the Resource.meta element be included in the diff", example = "false")
+		@OperationParam(name = ProviderConstants.DIFF_INCLUDE_META_PARAMETER, typeName = "boolean", min = 0, max = 1)
+			IPrimitiveType<Boolean> theIncludeMeta,
 		RequestDetails theRequestDetails) {
 
 		if (!Objects.equal(theFromVersion.getResourceType(), theToVersion.getResourceType())) {
@@ -98,13 +116,12 @@ public class DiffProvider {
 			throw new InvalidRequestException(msg);
 		}
 
-		IFhirResourceDao dao = myDaoRegistry.getResourceDao(theFromVersion.getResourceType());
+		IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(theFromVersion.getResourceType());
 		IBaseResource sourceResource = dao.read(theFromVersion, theRequestDetails);
 		IBaseResource targetResource = dao.read(theToVersion, theRequestDetails);
 
 		FhirPatch fhirPatch = newPatch(theIncludeMeta);
-		IBaseParameters diff = fhirPatch.diff(sourceResource, targetResource);
-		return diff;
+		return fhirPatch.diff(sourceResource, targetResource);
 	}
 
 	@Nonnull
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/GraphQLProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/GraphQLProvider.java
index f3d27f59a3e..ed68657798c 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/GraphQLProvider.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/GraphQLProvider.java
@@ -24,6 +24,7 @@ import ca.uhn.fhir.context.ConfigurationException;
 import ca.uhn.fhir.context.FhirContext;
 import ca.uhn.fhir.context.FhirVersionEnum;
 import ca.uhn.fhir.context.support.IValidationSupport;
+import ca.uhn.fhir.model.api.annotation.Description;
 import ca.uhn.fhir.rest.annotation.GraphQL;
 import ca.uhn.fhir.rest.annotation.GraphQLQueryBody;
 import ca.uhn.fhir.rest.annotation.GraphQLQueryUrl;
@@ -110,18 +111,20 @@ public class GraphQLProvider {
 		myStorageServices = theStorageServices;
 	}
 
+	@Description(value="This operation invokes a GraphQL expression for fetching an joining a graph of resources, returning them in a custom format.")
 	@GraphQL(type=RequestTypeEnum.GET)
-	public String processGraphQlGetRequest(ServletRequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQueryUrl String queryUrl) {
-		if (queryUrl != null) {
-			return processGraphQLRequest(theRequestDetails, theId, queryUrl);
+	public String processGraphQlGetRequest(ServletRequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQueryUrl String theQueryUrl) {
+		if (theQueryUrl != null) {
+			return processGraphQLRequest(theRequestDetails, theId, theQueryUrl);
 		}
 		throw new InvalidRequestException("Unable to parse empty GraphQL expression");
 	}
 
+	@Description(value="This operation invokes a GraphQL expression for fetching an joining a graph of resources, returning them in a custom format.")
 	@GraphQL(type=RequestTypeEnum.POST)
-	public String processGraphQlPostRequest(ServletRequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQueryBody String queryBody) {
-		if (queryBody != null) {
-			return processGraphQLRequest(theRequestDetails, theId, queryBody);
+	public String processGraphQlPostRequest(ServletRequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQueryBody String theQueryBody) {
+		if (theQueryBody != null) {
+			return processGraphQLRequest(theRequestDetails, theId, theQueryBody);
 		}
 		throw new InvalidRequestException("Unable to parse empty GraphQL expression");
 	}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaCapabilityStatementProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaCapabilityStatementProvider.java
index 07c0fd5769c..08c123988cd 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaCapabilityStatementProvider.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaCapabilityStatementProvider.java
@@ -80,6 +80,11 @@ public class JpaCapabilityStatementProvider extends ServerCapabilityStatementPro
 		if (isNotBlank(myImplementationDescription)) {
 			theTerser.setElement(theCapabilityStatement, "implementation.description", myImplementationDescription);
 		}
+
+		theTerser.addElement(theCapabilityStatement, "patchFormat", Constants.CT_FHIR_JSON_NEW);
+		theTerser.addElement(theCapabilityStatement, "patchFormat", Constants.CT_FHIR_XML_NEW);
+		theTerser.addElement(theCapabilityStatement, "patchFormat", Constants.CT_JSON_PATCH);
+		theTerser.addElement(theCapabilityStatement, "patchFormat", Constants.CT_XML_PATCH);
 	}
 
 	@Override
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java
index 92b926c8f10..53f0ca41d3e 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java
@@ -63,129 +63,4 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
 		super(theDao);
 	}
 
-	@Create
-	public MethodOutcome create(HttpServletRequest theRequest, @ResourceParam T theResource, @ConditionalUrlParam String theConditional, RequestDetails theRequestDetails) {
-		startRequest(theRequest);
-		try {
-			if (theConditional != null) {
-				return getDao().create(theResource, theConditional, theRequestDetails);
-			} else {
-				return getDao().create(theResource, theRequestDetails);
-			}
-		} finally {
-			endRequest(theRequest);
-		}
-	}
-
-	@Delete()
-	public MethodOutcome delete(HttpServletRequest theRequest, @IdParam IdDt theResource, @ConditionalUrlParam(supportsMultiple = true) String theConditional, RequestDetails theRequestDetails) {
-		startRequest(theRequest);
-		try {
-			if (theConditional != null) {
-				return getDao().deleteByUrl(theConditional, theRequestDetails);
-			} else {
-				return getDao().delete(theResource, theRequestDetails);
-			}
-		} finally {
-			endRequest(theRequest);
-		}
-	}
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerDt.class)
-	})
-	public Parameters expunge(
-		@IdParam IIdType theIdParam,
-		@OperationParam(name = OPERATION_EXPUNGE_PARAM_LIMIT) IntegerDt theLimit,
-		@OperationParam(name = OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanDt theExpungeDeletedResources,
-		@OperationParam(name = OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanDt theExpungeOldVersions,
-		RequestDetails theRequest) {
-		org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theIdParam, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null, theRequest);
-		return JpaSystemProviderDstu2.toExpungeResponse(retVal);
-	}
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerDt.class)
-	})
-	public Parameters expunge(
-		@OperationParam(name = OPERATION_EXPUNGE_PARAM_LIMIT) IntegerDt theLimit,
-		@OperationParam(name = OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanDt theExpungeDeletedResources,
-		@OperationParam(name = OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanDt theExpungeOldVersions,
-		RequestDetails theRequest) {
-		org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(null, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null, theRequest);
-		return JpaSystemProviderDstu2.toExpungeResponse(retVal);
-	}
-
-	@Operation(name = OPERATION_META, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = MetaDt.class)
-	})
-	public Parameters meta(RequestDetails theRequestDetails) {
-		Parameters parameters = new Parameters();
-		MetaDt metaGetOperation = getDao().metaGetOperation(MetaDt.class, theRequestDetails);
-		parameters.addParameter().setName("return").setValue(metaGetOperation);
-		return parameters;
-	}
-
-	@Operation(name = OPERATION_META, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = MetaDt.class)
-	})
-	public Parameters meta(@IdParam IdDt theId, RequestDetails theRequestDetails) {
-		Parameters parameters = new Parameters();
-		MetaDt metaGetOperation = getDao().metaGetOperation(MetaDt.class, theId, theRequestDetails);
-		parameters.addParameter().setName("return").setValue(metaGetOperation);
-		return parameters;
-	}
-
-	@Operation(name = OPERATION_META_ADD, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = MetaDt.class)
-	})
-	public Parameters metaAdd(@IdParam IdDt theId, @OperationParam(name = "meta") MetaDt theMeta, RequestDetails theRequestDetails) {
-		if (theMeta == null) {
-			throw new InvalidRequestException("Input contains no parameter with name 'meta'");
-		}
-		Parameters parameters = new Parameters();
-		MetaDt metaAddOperation = getDao().metaAddOperation(theId, theMeta, theRequestDetails);
-		parameters.addParameter().setName("return").setValue(metaAddOperation);
-		return parameters;
-	}
-
-	@Operation(name = OPERATION_META_DELETE, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = MetaDt.class)
-	})
-	public Parameters metaDelete(@IdParam IdDt theId, @OperationParam(name = "meta") MetaDt theMeta, RequestDetails theRequestDetails) {
-		if (theMeta == null) {
-			throw new InvalidRequestException("Input contains no parameter with name 'meta'");
-		}
-		Parameters parameters = new Parameters();
-		parameters.addParameter().setName("return").setValue(getDao().metaDeleteOperation(theId, theMeta, theRequestDetails));
-		return parameters;
-	}
-
-	@Update
-	public MethodOutcome update(HttpServletRequest theRequest, @ResourceParam T theResource, @IdParam IdDt theId, @ConditionalUrlParam String theConditional, RequestDetails theRequestDetails) {
-		startRequest(theRequest);
-		try {
-			if (theConditional != null) {
-				return getDao().update(theResource, theConditional, theRequestDetails);
-			} else {
-				theResource.setId(theId);
-				return getDao().update(theResource, theRequestDetails);
-			}
-		} finally {
-			endRequest(theRequest);
-		}
-	}
-
-	@Validate
-	public MethodOutcome validate(@ResourceParam T theResource, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
-											@Validate.Profile String theProfile, RequestDetails theRequestDetails) {
-		return validate(theResource, null, theRawResource, theEncoding, theMode, theProfile, theRequestDetails);
-	}
-
-	@Validate
-	public MethodOutcome validate(@ResourceParam T theResource, @IdParam IdDt theId, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
-											@Validate.Profile String theProfile, RequestDetails theRequestDetails) {
-		return getDao().validate(theResource, theId, theRawResource, theEncoding, theMode, theProfile, theRequestDetails);
-	}
-
 }
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java
index 538e7a9a283..b7c5f32596b 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java
@@ -1,38 +1,30 @@
 package ca.uhn.fhir.jpa.provider;
 
 import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
-import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
 import ca.uhn.fhir.jpa.model.util.JpaConstants;
 import ca.uhn.fhir.model.api.annotation.Description;
 import ca.uhn.fhir.model.dstu2.composite.MetaDt;
 import ca.uhn.fhir.model.dstu2.resource.Bundle;
 import ca.uhn.fhir.model.dstu2.resource.Parameters;
 import ca.uhn.fhir.model.dstu2.resource.Parameters.Parameter;
-import ca.uhn.fhir.model.primitive.BooleanDt;
 import ca.uhn.fhir.model.primitive.IntegerDt;
 import ca.uhn.fhir.model.primitive.StringDt;
-import ca.uhn.fhir.rest.annotation.IdParam;
 import ca.uhn.fhir.rest.annotation.Operation;
 import ca.uhn.fhir.rest.annotation.OperationParam;
 import ca.uhn.fhir.rest.annotation.Transaction;
 import ca.uhn.fhir.rest.annotation.TransactionParam;
 import ca.uhn.fhir.rest.api.server.RequestDetails;
 import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
-import org.hl7.fhir.instance.model.api.IBaseBundle;
-import org.hl7.fhir.instance.model.api.IIdType;
 import org.hl7.fhir.r4.model.IntegerType;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 
-import javax.servlet.http.HttpServletRequest;
 import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.TreeMap;
 
 import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
-import static org.apache.commons.lang3.StringUtils.isBlank;
 
 /*
  * #%L
@@ -60,38 +52,6 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProviderDstu2Plus<Bundl
 	@Qualifier("mySystemDaoDstu2")
 	private IFhirSystemDao<Bundle, MetaDt> mySystemDao;
 
-	@Autowired(required = false)
-	private IFulltextSearchSvc mySearchDao;
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerDt.class)
-	})
-	public Parameters expunge(
-		@IdParam IIdType theIdParam,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerDt theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanDt theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanDt theExpungeOldVersions,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanDt theExpungeEverything,
-		RequestDetails theRequestDetails
-	) {
-		org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything, theRequestDetails);
-		return toExpungeResponse(retVal);
-	}
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerDt.class)
-	})
-	public Parameters expunge(
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerDt theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanDt theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanDt theExpungeOldVersions,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanDt theExpungeEverything,
-		RequestDetails theRequestDetails
-	) {
-		org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything, theRequestDetails);
-		return toExpungeResponse(retVal);
-	}
-
 	//@formatter:off
 	// This is generated by hand:
 	// ls hapi-fhir-structures-dstu2/target/generated-sources/tinder/ca/uhn/fhir/model/dstu2/resource/ | sort | sed "s/.java//" | sed "s/^/@OperationParam(name=\"/" | sed "s/$/\", type=IntegerDt.class, min=0, max=1),/"
@@ -222,28 +182,6 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProviderDstu2Plus<Bundl
 		}
 	}
 
-	/**
-	 * /$process-message
-	 */
-	@Operation(name = JpaConstants.OPERATION_PROCESS_MESSAGE, idempotent = false)
-	public IBaseBundle processMessage(
-		HttpServletRequest theServletRequest,
-		RequestDetails theRequestDetails,
-
-		@OperationParam(name = "content", min = 1, max = 1)
-		@Description(formalDefinition = "The message to process (or, if using asynchronous messaging, it may be a response message to accept)")
-			Bundle theMessageToProcess
-	) {
-
-		startRequest(theServletRequest);
-		try {
-			return getDao().processMessage(theRequestDetails, theMessageToProcess);
-		} finally {
-			endRequest(theServletRequest);
-		}
-
-	}
-
 	public static Parameters toExpungeResponse(org.hl7.fhir.r4.model.Parameters theRetVal) {
 		Integer count = ((IntegerType) theRetVal.getParameterFirstRep().getValue()).getValue();
 		return new Parameters()
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaResourceProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaResourceProviderDstu3.java
index 12d1b58c506..e85c5c23af6 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaResourceProviderDstu3.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaResourceProviderDstu3.java
@@ -64,136 +64,4 @@ public class JpaResourceProviderDstu3<T extends IAnyResource> extends BaseJpaRes
 		super(theDao);
 	}
 
-	@Create
-	public MethodOutcome create(HttpServletRequest theRequest, @ResourceParam T theResource, @ConditionalUrlParam String theConditional, RequestDetails theRequestDetails) {
-		startRequest(theRequest);
-		try {
-			if (theConditional != null) {
-				return getDao().create(theResource, theConditional, theRequestDetails);
-			} else {
-				return getDao().create(theResource, theRequestDetails);
-			}
-		} finally {
-			endRequest(theRequest);
-		}
-	}
-
-	@Delete()
-	public MethodOutcome delete(HttpServletRequest theRequest, @IdParam IdType theResource, @ConditionalUrlParam(supportsMultiple = true) String theConditional, RequestDetails theRequestDetails) {
-		startRequest(theRequest);
-		try {
-			if (theConditional != null) {
-				return getDao().deleteByUrl(theConditional, theRequestDetails);
-			} else {
-				return getDao().delete(theResource, theRequestDetails);
-			}
-		} finally {
-			endRequest(theRequest);
-		}
-	}
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
-	})
-	public Parameters expunge(
-		@IdParam IIdType theIdParam,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
-		RequestDetails theRequest) {
-		org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theIdParam, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null, theRequest);
-		try {
-			return convertParameters(retVal);
-		} catch (FHIRException e) {
-			throw new InternalErrorException(e);
-		}
-	}
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
-	})
-	public Parameters expunge(
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
-		RequestDetails theRequest) {
-		org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(null, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null, theRequest);
-		try {
-			return convertParameters(retVal);
-		} catch (FHIRException e) {
-			throw new InternalErrorException(e);
-		}
-	}
-
-	@Operation(name = OPERATION_META, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = Meta.class)
-	})
-	public Parameters meta(RequestDetails theRequestDetails) {
-		Parameters parameters = new Parameters();
-		Meta metaGetOperation = getDao().metaGetOperation(Meta.class, theRequestDetails);
-		parameters.addParameter().setName("return").setValue(metaGetOperation);
-		return parameters;
-	}
-
-	@Operation(name = OPERATION_META, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = Meta.class)
-	})
-	public Parameters meta(@IdParam IdType theId, RequestDetails theRequestDetails) {
-		Parameters parameters = new Parameters();
-		Meta metaGetOperation = getDao().metaGetOperation(Meta.class, theId, theRequestDetails);
-		parameters.addParameter().setName("return").setValue(metaGetOperation);
-		return parameters;
-	}
-
-	@Operation(name = OPERATION_META_ADD, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = Meta.class)
-	})
-	public Parameters metaAdd(@IdParam IdType theId, @OperationParam(name = "meta") Meta theMeta, RequestDetails theRequestDetails) {
-		if (theMeta == null) {
-			throw new InvalidRequestException("Input contains no parameter with name 'meta'");
-		}
-		Parameters parameters = new Parameters();
-		Meta metaAddOperation = getDao().metaAddOperation(theId, theMeta, theRequestDetails);
-		parameters.addParameter().setName("return").setValue(metaAddOperation);
-		return parameters;
-	}
-
-	@Operation(name = OPERATION_META_DELETE, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = Meta.class)
-	})
-	public Parameters metaDelete(@IdParam IdType theId, @OperationParam(name = "meta") Meta theMeta, RequestDetails theRequestDetails) {
-		if (theMeta == null) {
-			throw new InvalidRequestException("Input contains no parameter with name 'meta'");
-		}
-		Parameters parameters = new Parameters();
-		parameters.addParameter().setName("return").setValue(getDao().metaDeleteOperation(theId, theMeta, theRequestDetails));
-		return parameters;
-	}
-
-	@Update
-	public MethodOutcome update(HttpServletRequest theRequest, @ResourceParam T theResource, @IdParam IdType theId, @ConditionalUrlParam String theConditional, RequestDetails theRequestDetails) {
-		startRequest(theRequest);
-		try {
-			if (theConditional != null) {
-				return getDao().update(theResource, theConditional, theRequestDetails);
-			} else {
-				return getDao().update(theResource, theRequestDetails);
-			}
-		} finally {
-			endRequest(theRequest);
-		}
-	}
-
-	@Validate
-	public MethodOutcome validate(@ResourceParam T theResource, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
-											@Validate.Profile String theProfile, RequestDetails theRequestDetails) {
-		return validate(theResource, null, theRawResource, theEncoding, theMode, theProfile, theRequestDetails);
-	}
-
-	@Validate
-	public MethodOutcome validate(@ResourceParam T theResource, @IdParam IdType theId, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
-											@Validate.Profile String theProfile, RequestDetails theRequestDetails) {
-		return getDao().validate(theResource, theId, theRawResource, theEncoding, theMode, theProfile, theRequestDetails);
-	}
-
 }
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java
index 7cd6746cd48..18f89c03889 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/JpaSystemProviderDstu3.java
@@ -65,46 +65,6 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundl
 	@Qualifier("mySystemDaoDstu3")
 	private IFhirSystemDao<Bundle, Meta> mySystemDao;
 
-	@Autowired(required = false)
-	private IFulltextSearchSvc mySearchDao;
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
-	})
-	public Parameters expunge(
-		@IdParam IIdType theIdParam,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanType theExpungeEverything,
-		RequestDetails theRequestDetails
-	) {
-		org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything, theRequestDetails);
-		try {
-			return convertParameters(retVal);
-		} catch (FHIRException e) {
-			throw new InternalErrorException(e);
-		}
-	}
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
-	})
-	public Parameters expunge(
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanType theExpungeEverything,
-		RequestDetails theRequestDetails
-	) {
-		org.hl7.fhir.r4.model.Parameters retVal = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything, theRequestDetails);
-		try {
-			return convertParameters(retVal);
-		} catch (FHIRException e) {
-			throw new InternalErrorException(e);
-		}
-	}
-
 	// This is generated by hand:
 	// ls hapi-fhir-structures-dstu2/target/generated-sources/tinder/ca/uhn/fhir/model/dstu2/resource/ | sort | sed "s/.java//" | sed "s/^/@OperationParam(name=\"/" | sed "s/$/\", type=IntegerType.class, min=0, max=1),/"
 	@Operation(name = JpaConstants.OPERATION_GET_RESOURCE_COUNTS, idempotent = true, returnParameters = {
@@ -233,32 +193,4 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundl
 		}
 	}
 
-	/**
-	 * /$process-message
-	 */
-	@Operation(name = JpaConstants.OPERATION_PROCESS_MESSAGE, idempotent = false)
-	public IBaseBundle processMessage(
-		HttpServletRequest theServletRequest,
-		RequestDetails theRequestDetails,
-
-		@OperationParam(name = "content", min = 1, max = 1)
-		@Description(formalDefinition = "The message to process (or, if using asynchronous messaging, it may be a response message to accept)")
-			Bundle theMessageToProcess
-	) {
-
-		startRequest(theServletRequest);
-		try {
-			return getDao().processMessage(theRequestDetails, theMessageToProcess);
-		} finally {
-			endRequest(theServletRequest);
-		}
-
-	}
-
-	public static void validateFulltextSearchEnabled(IFulltextSearchSvc theSearchDao) {
-		if (theSearchDao == null || theSearchDao.isDisabled()) {
-			throw new InvalidRequestException("Fulltext searching is disabled on this server");
-		}
-	}
-
 }
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaResourceProviderR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaResourceProviderR4.java
index 0c701c6f7e0..034f5956efd 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaResourceProviderR4.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaResourceProviderR4.java
@@ -61,128 +61,4 @@ public class JpaResourceProviderR4<T extends IAnyResource> extends BaseJpaResour
 		super(theDao);
 	}
 
-	@Create
-	public MethodOutcome create(HttpServletRequest theRequest, @ResourceParam T theResource, @ConditionalUrlParam String theConditional, RequestDetails theRequestDetails) {
-		startRequest(theRequest);
-		try {
-			if (theConditional != null) {
-				return getDao().create(theResource, theConditional, theRequestDetails);
-			} else {
-				return getDao().create(theResource, theRequestDetails);
-			}
-		} finally {
-			endRequest(theRequest);
-		}
-	}
-
-	@Delete()
-	public MethodOutcome delete(HttpServletRequest theRequest, @IdParam IdType theResource, @ConditionalUrlParam(supportsMultiple = true) String theConditional, RequestDetails theRequestDetails) {
-		startRequest(theRequest);
-		try {
-			if (theConditional != null) {
-				return getDao().deleteByUrl(theConditional, theRequestDetails);
-			} else {
-				return getDao().delete(theResource, theRequestDetails);
-			}
-		} finally {
-			endRequest(theRequest);
-		}
-	}
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
-	})
-	public Parameters expunge(
-		@IdParam IIdType theIdParam,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
-		RequestDetails theRequest) {
-		return super.doExpunge(theIdParam, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null, theRequest);
-	}
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
-	})
-	public Parameters expunge(
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
-		RequestDetails theRequest) {
-		return super.doExpunge(null, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null, theRequest);
-	}
-
-
-
-	@Operation(name = OPERATION_META, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = Meta.class)
-	})
-	public Parameters meta(RequestDetails theRequestDetails) {
-		Parameters parameters = new Parameters();
-		Meta metaGetOperation = getDao().metaGetOperation(Meta.class, theRequestDetails);
-		parameters.addParameter().setName("return").setValue(metaGetOperation);
-		return parameters;
-	}
-
-	@Operation(name = OPERATION_META, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = Meta.class)
-	})
-	public Parameters meta(@IdParam IdType theId, RequestDetails theRequestDetails) {
-		Parameters parameters = new Parameters();
-		Meta metaGetOperation = getDao().metaGetOperation(Meta.class, theId, theRequestDetails);
-		parameters.addParameter().setName("return").setValue(metaGetOperation);
-		return parameters;
-	}
-
-	@Operation(name = OPERATION_META_ADD, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = Meta.class)
-	})
-	public Parameters metaAdd(@IdParam IdType theId, @OperationParam(name = "meta") Meta theMeta, RequestDetails theRequestDetails) {
-		if (theMeta == null) {
-			throw new InvalidRequestException("Input contains no parameter with name 'meta'");
-		}
-		Parameters parameters = new Parameters();
-		Meta metaAddOperation = getDao().metaAddOperation(theId, theMeta, theRequestDetails);
-		parameters.addParameter().setName("return").setValue(metaAddOperation);
-		return parameters;
-	}
-
-	@Operation(name = OPERATION_META_DELETE, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = Meta.class)
-	})
-	public Parameters metaDelete(@IdParam IdType theId, @OperationParam(name = "meta") Meta theMeta, RequestDetails theRequestDetails) {
-		if (theMeta == null) {
-			throw new InvalidRequestException("Input contains no parameter with name 'meta'");
-		}
-		Parameters parameters = new Parameters();
-		parameters.addParameter().setName("return").setValue(getDao().metaDeleteOperation(theId, theMeta, theRequestDetails));
-		return parameters;
-	}
-
-	@Update
-	public MethodOutcome update(HttpServletRequest theRequest, @ResourceParam T theResource, @IdParam IdType theId, @ConditionalUrlParam String theConditional, RequestDetails theRequestDetails) {
-		startRequest(theRequest);
-		try {
-			if (theConditional != null) {
-				return getDao().update(theResource, theConditional, theRequestDetails);
-			} else {
-				return getDao().update(theResource, theRequestDetails);
-			}
-		} finally {
-			endRequest(theRequest);
-		}
-	}
-
-	@Validate
-	public MethodOutcome validate(@ResourceParam T theResource, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
-											@Validate.Profile String theProfile, RequestDetails theRequestDetails) {
-		return validate(theResource, null, theRawResource, theEncoding, theMode, theProfile, theRequestDetails);
-	}
-
-	@Validate
-	public MethodOutcome validate(@ResourceParam T theResource, @IdParam IdType theId, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
-											@Validate.Profile String theProfile, RequestDetails theRequestDetails) {
-		return getDao().validate(theResource, theId, theRawResource, theEncoding, theMode, theProfile, theRequestDetails);
-	}
-
 }
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java
index 662efc47afd..b77a4a05c54 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/JpaSystemProviderR4.java
@@ -1,40 +1,28 @@
 package ca.uhn.fhir.jpa.provider.r4;
 
 import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
-import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
 import ca.uhn.fhir.jpa.model.util.JpaConstants;
 import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus;
 import ca.uhn.fhir.model.api.annotation.Description;
-import ca.uhn.fhir.rest.annotation.IdParam;
 import ca.uhn.fhir.rest.annotation.Operation;
 import ca.uhn.fhir.rest.annotation.OperationParam;
 import ca.uhn.fhir.rest.annotation.Transaction;
 import ca.uhn.fhir.rest.annotation.TransactionParam;
 import ca.uhn.fhir.rest.api.server.RequestDetails;
-import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
 import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
-import org.hl7.fhir.instance.model.api.IBaseBundle;
-import org.hl7.fhir.instance.model.api.IIdType;
-import org.hl7.fhir.r4.model.BooleanType;
 import org.hl7.fhir.r4.model.Bundle;
-import org.hl7.fhir.r4.model.DecimalType;
 import org.hl7.fhir.r4.model.IntegerType;
 import org.hl7.fhir.r4.model.Meta;
 import org.hl7.fhir.r4.model.Parameters;
-import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
-import org.hl7.fhir.r4.model.StringType;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 
-import javax.servlet.http.HttpServletRequest;
 import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.TreeMap;
 
 import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
-import static org.apache.commons.lang3.StringUtils.isBlank;
 
 /*
  * #%L
@@ -62,36 +50,6 @@ public class JpaSystemProviderR4 extends BaseJpaSystemProviderDstu2Plus<Bundle,
 	@Qualifier("mySystemDaoR4")
 	private IFhirSystemDao<Bundle, Meta> mySystemDao;
 
-	@Autowired(required = false)
-	private IFulltextSearchSvc mySearchDao;
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
-	})
-	public Parameters expunge(
-		@IdParam IIdType theIdParam,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanType theExpungeEverything,
-		RequestDetails theRequestDetails
-	) {
-		return super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything, theRequestDetails);
-	}
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
-	})
-	public Parameters expunge(
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanType theExpungeEverything,
-		RequestDetails theRequestDetails
-	) {
-		return super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything, theRequestDetails);
-	}
-
 	// This is generated by hand:
 	// ls hapi-fhir-structures-dstu2/target/generated-sources/tinder/ca/uhn/fhir/model/dstu2/resource/ | sort | sed "s/.java//" | sed "s/^/@OperationParam(name=\"/" | sed "s/$/\", type=IntegerType.class, min=0, max=1),/"
 	@Operation(name = JpaConstants.OPERATION_GET_RESOURCE_COUNTS, idempotent = true, returnParameters = {
@@ -210,28 +168,6 @@ public class JpaSystemProviderR4 extends BaseJpaSystemProviderDstu2Plus<Bundle,
 		return parameters;
 	}
 
-	/**
-	 * /$process-message
-	 */
-	@Operation(name = JpaConstants.OPERATION_PROCESS_MESSAGE, idempotent = false)
-	public IBaseBundle processMessage(
-		HttpServletRequest theServletRequest,
-		RequestDetails theRequestDetails,
-
-		@OperationParam(name = "content", min = 1, max = 1)
-		@Description(formalDefinition = "The message to process (or, if using asynchronous messaging, it may be a response message to accept)")
-			Bundle theMessageToProcess
-	) {
-
-		startRequest(theServletRequest);
-		try {
-			return getDao().processMessage(theRequestDetails, theMessageToProcess);
-		} finally {
-			endRequest(theServletRequest);
-		}
-
-	}
-
 	@Transaction
 	public Bundle transaction(RequestDetails theRequestDetails, @TransactionParam Bundle theResources) {
 		startRequest(((ServletRequestDetails) theRequestDetails).getServletRequest());
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaResourceProviderR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaResourceProviderR5.java
index 6f78a303b44..c31a1ca0f0e 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaResourceProviderR5.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaResourceProviderR5.java
@@ -61,130 +61,4 @@ public class JpaResourceProviderR5<T extends IAnyResource> extends BaseJpaResour
 		super(theDao);
 	}
 
-	@Create
-	public MethodOutcome create(HttpServletRequest theRequest, @ResourceParam T theResource, @ConditionalUrlParam String theConditional, RequestDetails theRequestDetails) {
-		startRequest(theRequest);
-		try {
-			if (theConditional != null) {
-				return getDao().create(theResource, theConditional, theRequestDetails);
-			} else {
-				return getDao().create(theResource, theRequestDetails);
-			}
-		} finally {
-			endRequest(theRequest);
-		}
-	}
-
-	@Delete()
-	public MethodOutcome delete(HttpServletRequest theRequest, @IdParam IdType theResource, @ConditionalUrlParam(supportsMultiple = true) String theConditional, RequestDetails theRequestDetails) {
-		startRequest(theRequest);
-		try {
-			if (theConditional != null) {
-				return getDao().deleteByUrl(theConditional, theRequestDetails);
-			} else {
-				return getDao().delete(theResource, theRequestDetails);
-			}
-		} finally {
-			endRequest(theRequest);
-		}
-	}
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
-	})
-	public Parameters expunge(
-		@IdParam IIdType theIdParam,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
-		RequestDetails theRequest) {
-
-		org.hl7.fhir.r4.model.Parameters parameters = super.doExpunge(theIdParam, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null, theRequest);
-		return org.hl7.fhir.convertors.conv40_50.Parameters40_50.convertParameters(parameters);
-
-	}
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
-	})
-	public Parameters expunge(
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
-		RequestDetails theRequest) {
-		org.hl7.fhir.r4.model.Parameters parameters = super.doExpunge(null, theLimit, theExpungeDeletedResources, theExpungeOldVersions, null, theRequest);
-		return org.hl7.fhir.convertors.conv40_50.Parameters40_50.convertParameters(parameters);
-	}
-
-	@Operation(name = OPERATION_META, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = Meta.class)
-	})
-	public Parameters meta(RequestDetails theRequestDetails) {
-		Parameters parameters = new Parameters();
-		Meta metaGetOperation = getDao().metaGetOperation(Meta.class, theRequestDetails);
-		parameters.addParameter().setName("return").setValue(metaGetOperation);
-		return parameters;
-	}
-
-	@Operation(name = OPERATION_META, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = Meta.class)
-	})
-	public Parameters meta(@IdParam IdType theId, RequestDetails theRequestDetails) {
-		Parameters parameters = new Parameters();
-		Meta metaGetOperation = getDao().metaGetOperation(Meta.class, theId, theRequestDetails);
-		parameters.addParameter().setName("return").setValue(metaGetOperation);
-		return parameters;
-	}
-
-	@Operation(name = OPERATION_META_ADD, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = Meta.class)
-	})
-	public Parameters metaAdd(@IdParam IdType theId, @OperationParam(name = "meta") Meta theMeta, RequestDetails theRequestDetails) {
-		if (theMeta == null) {
-			throw new InvalidRequestException("Input contains no parameter with name 'meta'");
-		}
-		Parameters parameters = new Parameters();
-		Meta metaAddOperation = getDao().metaAddOperation(theId, theMeta, theRequestDetails);
-		parameters.addParameter().setName("return").setValue(metaAddOperation);
-		return parameters;
-	}
-
-	@Operation(name = OPERATION_META_DELETE, idempotent = true, returnParameters = {
-		@OperationParam(name = "return", type = Meta.class)
-	})
-	public Parameters metaDelete(@IdParam IdType theId, @OperationParam(name = "meta") Meta theMeta, RequestDetails theRequestDetails) {
-		if (theMeta == null) {
-			throw new InvalidRequestException("Input contains no parameter with name 'meta'");
-		}
-		Parameters parameters = new Parameters();
-		parameters.addParameter().setName("return").setValue(getDao().metaDeleteOperation(theId, theMeta, theRequestDetails));
-		return parameters;
-	}
-
-	@Update
-	public MethodOutcome update(HttpServletRequest theRequest, @ResourceParam T theResource, @IdParam IdType theId, @ConditionalUrlParam String theConditional, RequestDetails theRequestDetails) {
-		startRequest(theRequest);
-		try {
-			if (theConditional != null) {
-				return getDao().update(theResource, theConditional, theRequestDetails);
-			} else {
-				return getDao().update(theResource, theRequestDetails);
-			}
-		} finally {
-			endRequest(theRequest);
-		}
-	}
-
-	@Validate
-	public MethodOutcome validate(@ResourceParam T theResource, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
-											@Validate.Profile String theProfile, RequestDetails theRequestDetails) {
-		return validate(theResource, null, theRawResource, theEncoding, theMode, theProfile, theRequestDetails);
-	}
-
-	@Validate
-	public MethodOutcome validate(@ResourceParam T theResource, @IdParam IdType theId, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
-											@Validate.Profile String theProfile, RequestDetails theRequestDetails) {
-		return getDao().validate(theResource, theId, theRawResource, theEncoding, theMode, theProfile, theRequestDetails);
-	}
-
 }
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaSystemProviderR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaSystemProviderR5.java
index 6cb29aa50f2..631645099b4 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaSystemProviderR5.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/JpaSystemProviderR5.java
@@ -62,38 +62,6 @@ public class JpaSystemProviderR5 extends BaseJpaSystemProviderDstu2Plus<Bundle,
 	@Qualifier("mySystemDaoR5")
 	private IFhirSystemDao<Bundle, Meta> mySystemDao;
 
-	@Autowired(required = false)
-	private IFulltextSearchSvc mySearchDao;
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
-	})
-	public Parameters expunge(
-		@IdParam IIdType theIdParam,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanType theExpungeEverything,
-		RequestDetails theRequestDetails
-	) {
-		org.hl7.fhir.r4.model.Parameters parameters = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything, theRequestDetails);
-		return org.hl7.fhir.convertors.conv40_50.Parameters40_50.convertParameters(parameters);
-	}
-
-	@Operation(name = JpaConstants.OPERATION_EXPUNGE, idempotent = false, returnParameters = {
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, type = IntegerType.class)
-	})
-	public Parameters expunge(
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_LIMIT) IntegerType theLimit,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES) BooleanType theExpungeDeletedResources,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_PREVIOUS_VERSIONS) BooleanType theExpungeOldVersions,
-		@OperationParam(name = JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_EVERYTHING) BooleanType theExpungeEverything,
-		RequestDetails theRequestDetails
-	) {
-		org.hl7.fhir.r4.model.Parameters parameters = super.doExpunge(theLimit, theExpungeDeletedResources, theExpungeOldVersions, theExpungeEverything, theRequestDetails);
-		return org.hl7.fhir.convertors.conv40_50.Parameters40_50.convertParameters(parameters);
-	}
-
 	// This is generated by hand:
 	// ls hapi-fhir-structures-dstu2/target/generated-sources/tinder/ca/uhn/fhir/model/dstu2/resource/ | sort | sed "s/.java//" | sed "s/^/@OperationParam(name=\"/" | sed "s/$/\", type=IntegerType.class, min=0, max=1),/"
 	@Operation(name = JpaConstants.OPERATION_GET_RESOURCE_COUNTS, idempotent = true, returnParameters = {
@@ -212,28 +180,6 @@ public class JpaSystemProviderR5 extends BaseJpaSystemProviderDstu2Plus<Bundle,
 		return parameters;
 	}
 
-	/**
-	 * /$process-message
-	 */
-	@Operation(name = JpaConstants.OPERATION_PROCESS_MESSAGE, idempotent = false)
-	public IBaseBundle processMessage(
-		HttpServletRequest theServletRequest,
-		RequestDetails theRequestDetails,
-
-		@OperationParam(name = "content", min = 1, max = 1)
-		@Description(formalDefinition = "The message to process (or, if using asynchronous messaging, it may be a response message to accept)")
-			Bundle theMessageToProcess
-	) {
-
-		startRequest(theServletRequest);
-		try {
-			return getDao().processMessage(theRequestDetails, theMessageToProcess);
-		} finally {
-			endRequest(theServletRequest);
-		}
-
-	}
-
 	@Transaction
 	public Bundle transaction(RequestDetails theRequestDetails, @TransactionParam Bundle theResources) {
 		startRequest(((ServletRequestDetails) theRequestDetails).getServletRequest());
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java
index c8814ae0e4a..13cfc0ce1f9 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java
@@ -52,6 +52,7 @@ import org.hl7.fhir.r4.model.Patient;
 import org.hl7.fhir.r4.model.Practitioner;
 import org.hl7.fhir.r4.model.PractitionerRole;
 import org.hl7.fhir.r4.model.SearchParameter;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
 import org.slf4j.Logger;
@@ -2172,6 +2173,39 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
 		assertEquals(1, StringUtils.countMatches(searchSql, "SP_VALUE_NORMALIZED"));
 	}
 
+	/*
+	 * Should try to get this down at some point
+	 */
+	@Test
+	@Disabled
+	public void testSearch_StringParam_SearchOnePartition_AddRevIncludes() {
+		addReadPartition(1);
+		addCreatePartition(1, null);
+		Organization org = new Organization();
+		org.setName("FOO");
+		org.setId("FOO-ORG");
+		myOrganizationDao.update(org, mySrd);
+
+		for (int i = 0; i < 50; i++) {
+			addCreatePartition(1, null);
+			PractitionerRole pr = new PractitionerRole();
+			pr.getOrganization().setReference("Organization/FOO-ORG");
+			myPractitionerRoleDao.create(pr, mySrd);
+		}
+
+		addReadPartition(1);
+
+		myCaptureQueriesListener.clear();
+		SearchParameterMap map = new SearchParameterMap();
+		map.addRevInclude(PractitionerRole.INCLUDE_ORGANIZATION);
+		map.setCount(10);
+		IBundleProvider results = myOrganizationDao.search(map, mySrd);
+		List<IIdType> ids = toUnqualifiedVersionlessIds(results);
+		myCaptureQueriesListener.logSelectQueries();
+
+		assertEquals(10, ids.size(), () -> ids.toString());
+	}
+
 	@Test
 	public void testSearch_TagNotParam_SearchAllPartitions() {
 		IIdType patientIdNull = createPatient(withPartition(null), withActiveTrue(), withTag("http://system", "code"), withIdentifier("http://foo", "bar"));
@@ -2914,7 +2948,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
 		myResourceReindexingSvc.markAllResourcesForReindexing();
 		myResourceReindexingSvc.forceReindexingPass();
 
-		runInTransaction(()->{
+		runInTransaction(() -> {
 			assertNotEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXING_FAILED, myResourceTableDao.findById(patientIdNull.getIdPartAsLong()).get().getIndexStatus());
 			assertNotEquals(BaseHapiFhirDao.INDEX_STATUS_INDEXING_FAILED, myResourceTableDao.findById(patientId1.getIdPartAsLong()).get().getIndexStatus());
 		});
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderDstu2Test.java
index d1dbf9d1f30..bcaba0b3547 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderDstu2Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderDstu2Test.java
@@ -23,6 +23,7 @@ import com.google.common.base.Charsets;
 import org.apache.commons.io.IOUtils;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
@@ -387,8 +388,8 @@ public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
 
 	@Test
 	public void testMarkResourcesForReindexing() throws Exception {
-		HttpGet get = new HttpGet(ourServerBase + "/$mark-all-resources-for-reindexing");
-		CloseableHttpResponse http = ourHttpClient.execute(get);
+		HttpPost post = new HttpPost(ourServerBase + "/$mark-all-resources-for-reindexing");
+		CloseableHttpResponse http = ourHttpClient.execute(post);
 		try {
 			String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
 			ourLog.info(output);
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ServerDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ServerDstu3Test.java
index db6bde81d17..8a56f262375 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ServerDstu3Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ServerDstu3Test.java
@@ -9,6 +9,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.HashSet;
 import java.util.Set;
 
+import ca.uhn.fhir.rest.openapi.OpenApiInterceptor;
 import org.apache.commons.io.IOUtils;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
@@ -16,6 +17,7 @@ import org.hl7.fhir.dstu3.model.CapabilityStatement;
 import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
 import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
 import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 
 import ca.uhn.fhir.util.TestUtil;
@@ -24,7 +26,13 @@ public class ServerDstu3Test extends BaseResourceProviderDstu3Test {
 
 	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerDstu3Test.class);
 
-	
+	@Override
+	@AfterEach
+	public void after() throws Exception {
+		super.after();
+		ourRestServer.getInterceptorService().unregisterInterceptorsIf(t->t instanceof OpenApiInterceptor);
+	}
+
 	
 	/**
 	 * See #519
@@ -61,5 +69,18 @@ public class ServerDstu3Test extends BaseResourceProviderDstu3Test {
 	}
 
 
+	@Test
+	public void testFetchOpenApi() throws IOException {
+		ourRestServer.registerInterceptor(new OpenApiInterceptor());
+
+		HttpGet get = new HttpGet(ourServerBase + "/api-docs");
+		try (CloseableHttpResponse response = ourHttpClient.execute(get)) {
+			String string = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+			ourLog.info(string);
+
+			assertEquals(200, response.getStatusLine().getStatusCode());
+		}
+	}
+
 
 }
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/OpenApiInterceptorJpaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/OpenApiInterceptorJpaTest.java
new file mode 100644
index 00000000000..5ca0f688fca
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/OpenApiInterceptorJpaTest.java
@@ -0,0 +1,48 @@
+package ca.uhn.fhir.jpa.provider.r4;
+
+import ca.uhn.fhir.rest.openapi.OpenApiInterceptor;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class OpenApiInterceptorJpaTest extends BaseResourceProviderR4Test {
+
+	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OpenApiInterceptorJpaTest.class);
+
+	@Override
+	@AfterEach
+	public void after() throws Exception {
+		super.after();
+		ourRestServer.getInterceptorService().unregisterInterceptorsIf(t -> t instanceof OpenApiInterceptor);
+	}
+
+	@Test
+	public void testFetchOpenApi() throws IOException {
+		ourRestServer.registerInterceptor(new OpenApiInterceptor());
+
+		HttpGet get = new HttpGet(ourServerBase + "/metadata?_format=json&_pretty=true");
+		try (CloseableHttpResponse response = ourHttpClient.execute(get)) {
+			String string = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+			ourLog.info(string);
+
+			assertEquals(200, response.getStatusLine().getStatusCode());
+		}
+
+		get = new HttpGet(ourServerBase + "/api-docs");
+		try (CloseableHttpResponse response = ourHttpClient.execute(get)) {
+			String string = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+			ourLog.info(string);
+
+			assertEquals(200, response.getStatusLine().getStatusCode());
+		}
+	}
+
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerCapabilityStatementProviderJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerCapabilityStatementProviderJpaR4Test.java
index 9f4d6945e5c..eda0afa7e9c 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerCapabilityStatementProviderJpaR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerCapabilityStatementProviderJpaR4Test.java
@@ -17,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import javax.annotation.Nonnull;
 import java.io.IOException;
 import java.util.List;
+import java.util.TreeSet;
 import java.util.stream.Collectors;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -26,6 +27,7 @@ import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.hasItems;
 import static org.hamcrest.Matchers.not;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 
 public class ServerCapabilityStatementProviderJpaR4Test extends BaseResourceProviderR4Test {
 
@@ -39,6 +41,38 @@ public class ServerCapabilityStatementProviderJpaR4Test extends BaseResourceProv
 		assertThat(resourceTypes, hasItems("Patient", "Observation", "SearchParameter"));
 	}
 
+
+	@Test
+	public void testNoDuplicateResourceOperationNames() {
+		CapabilityStatement cs = myClient.capabilities().ofType(CapabilityStatement.class).execute();
+		ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(cs));
+		for (CapabilityStatement.CapabilityStatementRestResourceComponent next : cs.getRestFirstRep().getResource()) {
+			List<String> opNames = next
+				.getOperation()
+				.stream()
+				.map(t -> t.getName())
+				.sorted()
+				.collect(Collectors.toList());
+			ourLog.info("System ops: {}", opNames);
+			assertEquals(opNames.stream().distinct().sorted().collect(Collectors.toList()), opNames);
+		}
+	}
+
+	@Test
+	public void testNoDuplicateSystemOperationNames() {
+		CapabilityStatement cs = myClient.capabilities().ofType(CapabilityStatement.class).execute();
+		ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(cs));
+		List<String> systemOpNames = cs
+			.getRestFirstRep()
+			.getOperation()
+			.stream()
+			.map(t -> t.getName())
+			.sorted()
+			.collect(Collectors.toList());
+		ourLog.info("System ops: {}", systemOpNames);
+		assertEquals(systemOpNames.stream().distinct().sorted().collect(Collectors.toList()), systemOpNames);
+	}
+
 	@Test
 	public void testCustomSearchParamsReflectedInSearchParams() {
 		SearchParameter fooSp = new SearchParameter();
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java
index 0b7976947cb..759f065ed1f 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java
@@ -18,6 +18,7 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
 import ca.uhn.fhir.rest.api.Constants;
 import ca.uhn.fhir.rest.api.EncodingEnum;
 import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.client.apache.ResourceEntity;
 import ca.uhn.fhir.rest.client.api.IGenericClient;
 import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
 import ca.uhn.fhir.rest.param.ReferenceParam;
@@ -36,6 +37,7 @@ import org.apache.http.Header;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
 import org.apache.http.entity.ContentType;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
@@ -49,6 +51,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
 import org.hl7.fhir.r4.model.Bundle;
 import org.hl7.fhir.r4.model.Bundle.BundleType;
 import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
+import org.hl7.fhir.r4.model.CodeType;
 import org.hl7.fhir.r4.model.DecimalType;
 import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
 import org.hl7.fhir.r4.model.IdType;
@@ -75,6 +78,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.in;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.startsWith;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -247,8 +251,8 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
 
 	@Test
 	public void testMarkResourcesForReindexing() throws Exception {
-		HttpGet get = new HttpGet(ourServerBase + "/$mark-all-resources-for-reindexing");
-		CloseableHttpResponse http = ourHttpClient.execute(get);
+		HttpRequestBase post = new HttpPost(ourServerBase + "/$mark-all-resources-for-reindexing");
+		CloseableHttpResponse http = ourHttpClient.execute(post);
 		try {
 			String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
 			ourLog.info(output);
@@ -257,8 +261,8 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
 			IOUtils.closeQuietly(http);
 		}
 
-		get = new HttpGet(ourServerBase + "/$perform-reindexing-pass");
-		http = ourHttpClient.execute(get);
+		post = new HttpPost(ourServerBase + "/$perform-reindexing-pass");
+		http = ourHttpClient.execute(post);
 		try {
 			String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
 			ourLog.info(output);
@@ -272,8 +276,10 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
 
 	@Test
 	public void testMarkResourcesForReindexingTyped() throws Exception {
-		HttpGet get = new HttpGet(ourServerBase + "/$mark-all-resources-for-reindexing?type=Patient");
-		CloseableHttpResponse http = ourHttpClient.execute(get);
+
+		HttpPost post = new HttpPost(ourServerBase + "/$mark-all-resources-for-reindexing?type=Patient");
+		post.setEntity(new ResourceEntity(myFhirCtx, new Parameters().addParameter("type", new CodeType("Patient"))));
+		CloseableHttpResponse http = ourHttpClient.execute(post);
 		try {
 			String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
 			ourLog.info(output);
@@ -282,8 +288,9 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
 			IOUtils.closeQuietly(http);
 		}
 
-		get = new HttpGet(ourServerBase + "/$mark-all-resources-for-reindexing?type=FOO");
-		http = ourHttpClient.execute(get);
+		post = new HttpPost(ourServerBase + "/$mark-all-resources-for-reindexing?type=FOO");
+		post.setEntity(new ResourceEntity(myFhirCtx, new Parameters().addParameter("type", new CodeType("FOO"))));
+		http = ourHttpClient.execute(post);
 		try {
 			String output = IOUtils.toString(http.getEntity().getContent(), StandardCharsets.UTF_8);
 			ourLog.info(output);
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/lastn/LastNElasticsearchSvcMultipleObservationsIT.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/lastn/LastNElasticsearchSvcMultipleObservationsIT.java
index 8a837f94034..ae0bec25252 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/lastn/LastNElasticsearchSvcMultipleObservationsIT.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/lastn/LastNElasticsearchSvcMultipleObservationsIT.java
@@ -29,16 +29,6 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import org.junit.jupiter.api.extension.RegisterExtension;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 import org.testcontainers.elasticsearch.ElasticsearchContainer;
 import org.testcontainers.junit.jupiter.Container;
@@ -66,20 +56,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 public class LastNElasticsearchSvcMultipleObservationsIT {
 
 	static private final Calendar baseObservationDate = new GregorianCalendar();
-	private static ObjectMapper ourMapperNonPrettyPrint;
-
-	private static boolean indexLoaded = false;
-
-	private final Map<String, Map<String, List<Date>>> createdPatientObservationMap = new HashMap<>();
-
-	private final FhirContext myFhirContext = FhirContext.forCached(FhirVersionEnum.R4);
-
-
 	@Container
 	public static ElasticsearchContainer elasticsearchContainer = TestElasticsearchContainerHelper.getEmbeddedElasticSearch();
-
-
-
+	private static ObjectMapper ourMapperNonPrettyPrint;
+	private static boolean indexLoaded = false;
+	private final Map<String, Map<String, List<Date>>> createdPatientObservationMap = new HashMap<>();
+	private final FhirContext myFhirContext = FhirContext.forCached(FhirVersionEnum.R4);
 	private ElasticsearchSvcImpl elasticsearchSvc;
 
 	@BeforeEach
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java
index fe5f13de317..cd8b745e224 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java
@@ -40,12 +40,10 @@ import ca.uhn.fhir.util.StopWatch;
 import ca.uhn.fhir.validation.IInstanceValidatorModule;
 import com.google.common.collect.Lists;
 import org.hamcrest.Matchers;
-import org.hibernate.Session;
 import org.hibernate.internal.SessionImpl;
 import org.hl7.fhir.instance.model.api.IBaseResource;
 import org.hl7.fhir.r4.model.Bundle;
 import org.hl7.fhir.r4.model.ExplanationOfBenefit;
-import org.junit.Ignore;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Disabled;
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImplTest.java
index 45e7b561598..57f44b23eda 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImplTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImplTest.java
@@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.term;
 
 import org.junit.jupiter.api.Test;
 
-import static org.junit.Assert.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class BaseTermReadSvcImplTest {
diff --git a/hapi-fhir-jpaserver-batch/pom.xml b/hapi-fhir-jpaserver-batch/pom.xml
index 6b49017689b..d3df9443653 100644
--- a/hapi-fhir-jpaserver-batch/pom.xml
+++ b/hapi-fhir-jpaserver-batch/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-jpaserver-cql/pom.xml b/hapi-fhir-jpaserver-cql/pom.xml
index 9be25f3da9e..8f5317c6e83 100644
--- a/hapi-fhir-jpaserver-cql/pom.xml
+++ b/hapi-fhir-jpaserver-cql/pom.xml
@@ -7,7 +7,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
@@ -131,6 +131,11 @@
 			</exclusions>
 		</dependency>
 		<!-- test -->
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
 		<dependency>
 			<groupId>org.mockito</groupId>
 			<artifactId>mockito-core</artifactId>
@@ -144,13 +149,13 @@
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-test-utilities</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 			<scope>test</scope>
 		</dependency>
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-jpaserver-test-utilities</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 			<scope>test</scope>
 		</dependency>
  </dependencies>
diff --git a/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/common/helper/TranslatorHelperTest.java b/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/common/helper/TranslatorHelperTest.java
index c4a9393ab75..ed94139951b 100644
--- a/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/common/helper/TranslatorHelperTest.java
+++ b/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/common/helper/TranslatorHelperTest.java
@@ -13,11 +13,8 @@ import org.cqframework.cql.cql2elm.NamespaceManager;
 import org.cqframework.cql.elm.execution.Library;
 import org.cqframework.cql.elm.tracking.TrackBack;
 import org.hl7.elm.r1.VersionedIdentifier;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Matchers;
@@ -33,8 +30,11 @@ import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.reset;
@@ -61,7 +61,7 @@ public class TranslatorHelperTest implements CqlProviderTestBase {
 
 	//@BeforeEach
 	//@BeforeAll
-	@Before
+	@BeforeEach
 	public void createMocks() {
 		MockitoAnnotations.openMocks(this);
 		//libraryManager = Mockito.mock(LibraryManager.class);
@@ -85,7 +85,7 @@ public class TranslatorHelperTest implements CqlProviderTestBase {
 		when(libraryManager.getNamespaceManager()).thenReturn(namespaceManager);
 		when(namespaceManager.hasNamespaces()).thenReturn(false);
 		CqlTranslator translator = TranslatorHelper.getTranslator(sampleCql, libraryManager, modelManager);
-		assertNotNull("translator should not be NULL!", translator);
+		assertNotNull(translator, "translator should not be NULL!");
 	}
 
 	//@Test
@@ -95,7 +95,7 @@ public class TranslatorHelperTest implements CqlProviderTestBase {
 		when(libraryManager.getLibrarySourceLoader()).thenReturn(librarySourceLoader);
 		when(namespaceManager.hasNamespaces()).thenReturn(true);
 		CqlTranslator translator = TranslatorHelper.getTranslator(sampleCql, libraryManager, modelManager);
-		assertNotNull("translator should not be NULL!", translator);
+		assertNotNull(translator, "translator should not be NULL!");
 	}
 
 	//@Test
@@ -108,8 +108,8 @@ public class TranslatorHelperTest implements CqlProviderTestBase {
 		try {
 			translator = TranslatorHelper.getTranslator("   ", libraryManager, modelManager);
 			fail();
-		} catch(NullPointerException e) {
-			assertNull("translator should be NULL!", translator);
+		} catch (NullPointerException e) {
+			assertNull(translator, "translator should be NULL!");
 		}
 	}
 
@@ -133,9 +133,9 @@ public class TranslatorHelperTest implements CqlProviderTestBase {
 			when(CqlTranslator.fromStream(any(InputStream.class), any(ModelManager.class), any(LibraryManager.class), Matchers.<CqlTranslator.Options>anyVararg())).thenThrow(IOException.class);
 			translator = TranslatorHelper.getTranslator(new ByteArrayInputStream("INVALID-FILENAME".getBytes(StandardCharsets.UTF_8)), libraryManager, modelManager);
 			fail();
-		} catch(IllegalArgumentException | IOException e) {
+		} catch (IllegalArgumentException | IOException e) {
 			assertTrue(e instanceof IllegalArgumentException);
-			assertNull("translator should be NULL!", translator);
+			assertNull(translator, "translator should be NULL!");
 		}
 	}
 
@@ -153,11 +153,11 @@ public class TranslatorHelperTest implements CqlProviderTestBase {
 		Library library = null;
 		try {
 			library = TranslatorHelper.translateLibrary("INVALID-FILENAME", libraryManager, modelManager);
-		} catch(Exception e) {
+		} catch (Exception e) {
 			e.printStackTrace();
 			fail();
 		}
-		assertNotNull("library should not be NULL!", library);
+		assertNotNull(library, "library should not be NULL!");
 	}
 
 	@Test
@@ -166,8 +166,8 @@ public class TranslatorHelperTest implements CqlProviderTestBase {
 		try {
 			library = TranslatorHelper.readLibrary(new ByteArrayInputStream("INVALID-XML-DOCUMENT".getBytes()));
 			fail();
-		} catch(IllegalArgumentException e) {
-			assertNull("library should be NULL!", library);
+		} catch (IllegalArgumentException e) {
+			assertNull(library, "library should be NULL!");
 		}
 	}
 
@@ -181,7 +181,7 @@ public class TranslatorHelperTest implements CqlProviderTestBase {
 		} catch (IOException e) {
 			e.printStackTrace();
 		}
-		assertNotNull("library should not be NULL!", library);
+		assertNotNull(library, "library should not be NULL!");
 	}
 
 	@Test
diff --git a/hapi-fhir-jpaserver-mdm/pom.xml b/hapi-fhir-jpaserver-mdm/pom.xml
index 29d7f7a9503..f49c0de98fa 100644
--- a/hapi-fhir-jpaserver-mdm/pom.xml
+++ b/hapi-fhir-jpaserver-mdm/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
@@ -55,13 +55,13 @@
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-test-utilities</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 			<scope>test</scope>
 		</dependency>
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-jpaserver-test-utilities</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 			<scope>test</scope>
 		</dependency>
 		<dependency>
diff --git a/hapi-fhir-jpaserver-migrate/pom.xml b/hapi-fhir-jpaserver-migrate/pom.xml
index be260a03b39..aa549ab2385 100644
--- a/hapi-fhir-jpaserver-migrate/pom.xml
+++ b/hapi-fhir-jpaserver-migrate/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-jpaserver-model/pom.xml b/hapi-fhir-jpaserver-model/pom.xml
index 7a40544f634..189bf7f38da 100644
--- a/hapi-fhir-jpaserver-model/pom.xml
+++ b/hapi-fhir-jpaserver-model/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-jpaserver-searchparam/pom.xml b/hapi-fhir-jpaserver-searchparam/pom.xml
index 5bc11f73872..54c7f179c7b 100755
--- a/hapi-fhir-jpaserver-searchparam/pom.xml
+++ b/hapi-fhir-jpaserver-searchparam/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml
index 369918d69c8..cd087657fa9 100644
--- a/hapi-fhir-jpaserver-subscription/pom.xml
+++ b/hapi-fhir-jpaserver-subscription/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-jpaserver-test-utilities/pom.xml b/hapi-fhir-jpaserver-test-utilities/pom.xml
index 06b73aa7bac..5a330441e24 100644
--- a/hapi-fhir-jpaserver-test-utilities/pom.xml
+++ b/hapi-fhir-jpaserver-test-utilities/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml
index 9cec5a13cf8..407bad660b7 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml
+++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
@@ -47,6 +47,11 @@
 			<version>${project.version}</version>
 			<classifier>classes</classifier>
 		</dependency>
+        <dependency>
+            <groupId>ca.uhn.hapi.fhir</groupId>
+            <artifactId>hapi-fhir-server-openapi</artifactId>
+            <version>${project.version}</version>
+        </dependency>
 
 		<dependency>
 			<groupId>com.helger</groupId>
@@ -164,7 +169,7 @@
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-converter</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 		</dependency>
 
 	</dependencies>
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java
index a7e2c06d36a..caa380403cd 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java
@@ -19,6 +19,7 @@ import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
 import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
 import ca.uhn.fhir.jpa.provider.r5.JpaSystemProviderR5;
 import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
+import ca.uhn.fhir.rest.openapi.OpenApiInterceptor;
 import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
 import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig;
 import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
@@ -38,6 +39,7 @@ import ca.uhn.fhirtest.config.TestR4Config;
 import ca.uhn.fhirtest.config.TestR5Config;
 import ca.uhn.hapi.converters.server.VersionedApiConverterInterceptor;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
 import org.springframework.web.context.ContextLoaderListener;
 import org.springframework.web.context.WebApplicationContext;
 import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
@@ -79,11 +81,11 @@ public class TestRestfulServer extends RestfulServer {
 		WebApplicationContext parentAppCtx = ContextLoaderListener.getCurrentWebApplicationContext();
 
 		// These two parmeters are also declared in web.xml
-		String implDesc = getInitParameter("ImplementationDescription");
 		String fhirVersionParam = getInitParameter("FhirVersion");
-		if (StringUtils.isBlank(fhirVersionParam)) {
-			fhirVersionParam = "DSTU1";
-		}
+		Validate.notNull(fhirVersionParam);
+
+		setImplementationDescription("HAPI FHIR Test/Demo Server " + fhirVersionParam + " Endpoint");
+		setCopyright("This server is **Open Source Software**, licensed under the terms of the [Apache Software License 2.0](https://www.apache.org/licenses/LICENSE-2.0).");
 
 		// Depending on the version this server is supporing, we will
 		// retrieve all the appropriate resource providers and the
@@ -110,7 +112,6 @@ public class TestRestfulServer extends RestfulServer {
 				systemDao = myAppCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class);
 				etagSupport = ETagSupportEnum.ENABLED;
 				JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao, myAppCtx.getBean(DaoConfig.class));
-				confProvider.setImplementationDescription(implDesc);
 				setServerConformanceProvider(confProvider);
 				break;
 			}
@@ -127,7 +128,6 @@ public class TestRestfulServer extends RestfulServer {
 				systemDao = myAppCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class);
 				etagSupport = ETagSupportEnum.ENABLED;
 				JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, myAppCtx.getBean(DaoConfig.class), myAppCtx.getBean(ISearchParamRegistry.class));
-				confProvider.setImplementationDescription(implDesc);
 				setServerConformanceProvider(confProvider);
 				providers.add(myAppCtx.getBean(TerminologyUploaderProvider.class));
 				providers.add(myAppCtx.getBean(GraphQLProvider.class));
@@ -147,7 +147,6 @@ public class TestRestfulServer extends RestfulServer {
 				etagSupport = ETagSupportEnum.ENABLED;
 				IValidationSupport validationSupport = myAppCtx.getBean(IValidationSupport.class);
 				JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(this, systemDao, myAppCtx.getBean(DaoConfig.class), myAppCtx.getBean(ISearchParamRegistry.class), validationSupport);
-				confProvider.setImplementationDescription(implDesc);
 				setServerConformanceProvider(confProvider);
 				providers.add(myAppCtx.getBean(TerminologyUploaderProvider.class));
 				providers.add(myAppCtx.getBean(GraphQLProvider.class));
@@ -271,6 +270,10 @@ public class TestRestfulServer extends RestfulServer {
 		 */
 		registerProvider(myAppCtx.getBean(DiffProvider.class));
 
+		/*
+		 * OpenAPI
+		 */
+		registerInterceptor(new OpenApiInterceptor());
 	}
 
 	/**
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java
index cbaf424d495..fd1b72bec5a 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu2Config.java
@@ -109,7 +109,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
 		retVal.setUsername(myDbUsername);
 		retVal.setPassword(myDbPassword);
 		retVal.setDefaultQueryTimeout(20);
-		retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE);
+		retVal.setTestOnBorrow(true);
 
 		DataSource dataSource = ProxyDataSourceBuilder
 			.create(retVal)
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java
index bcbdb4a825f..a8d454a92e2 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestDstu3Config.java
@@ -125,7 +125,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
 		retVal.setUsername(myDbUsername);
 		retVal.setPassword(myDbPassword);
 		retVal.setDefaultQueryTimeout(20);
-		retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE);
+		retVal.setTestOnBorrow(true);
 
 		DataSource dataSource = ProxyDataSourceBuilder
 			.create(retVal)
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java
index f1194ba835d..e0db7abf4a9 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR4Config.java
@@ -109,7 +109,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
 		retVal.setUsername(myDbUsername);
 		retVal.setPassword(myDbPassword);
 		retVal.setDefaultQueryTimeout(20);
-		retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE);
+		retVal.setTestOnBorrow(true);
 
 		DataSource dataSource = ProxyDataSourceBuilder
 			.create(retVal)
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java
index f7f4d08b241..5fc6e03cde4 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/TestR5Config.java
@@ -109,7 +109,7 @@ public class TestR5Config extends BaseJavaConfigR5 {
 		retVal.setUsername(myDbUsername);
 		retVal.setPassword(myDbPassword);
 		retVal.setDefaultQueryTimeout(20);
-		retVal.setMaxConnLifetimeMillis(5 * DateUtils.MILLIS_PER_MINUTE);
+		retVal.setTestOnBorrow(true);
 
 		DataSource dataSource = ProxyDataSourceBuilder
 			.create(retVal)
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/web.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/web.xml
index 8fb6dbdfa9c..dd383fa7648 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/web.xml
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/web.xml
@@ -31,10 +31,6 @@
 			<param-name>contextConfigLocation</param-name>
 			<param-value>
 				ca.uhn.fhirtest.config.FhirTesterConfig
-				<!-- 
-				ca.uhn.fhir.jpa.config.WebsocketDstu2Config
-				ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3Config
-				-->
 			</param-value>
 		</init-param>
 		<load-on-startup>2</load-on-startup>
@@ -43,10 +39,6 @@
 	<servlet>
 		<servlet-name>fhirServletR5</servlet-name>
 		<servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class>
-		<init-param>
-			<param-name>ImplementationDescription</param-name>
-			<param-value>UHN Test Server (R5 Resources)</param-value>
-		</init-param>
 		<init-param>
 			<param-name>FhirVersion</param-name>
 			<param-value>R5</param-value>
@@ -57,10 +49,6 @@
 	<servlet>
 		<servlet-name>fhirServletR4</servlet-name>
 		<servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class>
-		<init-param>
-			<param-name>ImplementationDescription</param-name>
-			<param-value>UHN Test Server (R4 Resources)</param-value>
-		</init-param>
 		<init-param>
 			<param-name>FhirVersion</param-name>
 			<param-value>R4</param-value>
@@ -71,10 +59,6 @@
 	<servlet>
 		<servlet-name>fhirServletDstu2</servlet-name>
 		<servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class>
-		<init-param>
-			<param-name>ImplementationDescription</param-name>
-			<param-value>UHN Test Server (DSTU2 Resources)</param-value>
-		</init-param>
 		<init-param>
 			<param-name>FhirVersion</param-name>
 			<param-value>DSTU2</param-value>
@@ -85,10 +69,6 @@
 	<servlet>
 		<servlet-name>fhirServletDstu3</servlet-name>
 		<servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class>
-		<init-param>
-			<param-name>ImplementationDescription</param-name>
-			<param-value>UHN Test Server (STU3 Resources)</param-value>
-		</init-param>
 		<init-param>
 			<param-name>FhirVersion</param-name>
 			<param-value>DSTU3</param-value>
@@ -96,35 +76,6 @@
 		<load-on-startup>1</load-on-startup>
 	</servlet>
 
-<!--
-	<servlet>
-		<servlet-name>fhirServletTdl2</servlet-name>
-		<servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class>
-		<init-param>
-			<param-name>ImplementationDescription</param-name>
-			<param-value>Test Data Library (DSTU2 Resources)</param-value>
-		</init-param>
-		<init-param>
-			<param-name>FhirVersion</param-name>
-			<param-value>TDL2</param-value>
-		</init-param>
-		<load-on-startup>1</load-on-startup>
-	</servlet>
-	<servlet>
-		<servlet-name>fhirServletTdl3</servlet-name>
-		<servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class>
-		<init-param>
-			<param-name>ImplementationDescription</param-name>
-			<param-value>FHIR Test Data Library (STU3 Resources)</param-value>
-		</init-param>
-		<init-param>
-			<param-name>FhirVersion</param-name>
-			<param-value>TDL3</param-value>
-		</init-param>
-		<load-on-startup>1</load-on-startup>
-	</servlet>
--->
-
 	<servlet-mapping>
 		<servlet-name>fhirServletR5</servlet-name>
 		<url-pattern>/baseR5/*</url-pattern>
diff --git a/hapi-fhir-server-mdm/pom.xml b/hapi-fhir-server-mdm/pom.xml
index 1952a50d819..213863947bf 100644
--- a/hapi-fhir-server-mdm/pom.xml
+++ b/hapi-fhir-server-mdm/pom.xml
@@ -7,7 +7,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-server-openapi/pom.xml b/hapi-fhir-server-openapi/pom.xml
new file mode 100644
index 00000000000..59d85a5e228
--- /dev/null
+++ b/hapi-fhir-server-openapi/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+			xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<parent>
+		<groupId>ca.uhn.hapi.fhir</groupId>
+		<artifactId>hapi-deployable-pom</artifactId>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
+		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
+	</parent>
+
+	<modelVersion>4.0.0</modelVersion>
+
+	<artifactId>hapi-fhir-server-openapi</artifactId>
+
+	<dependencies>
+		<!-- HAPI FHIR -->
+		<dependency>
+			<groupId>ca.uhn.hapi.fhir</groupId>
+			<artifactId>hapi-fhir-server</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>ca.uhn.hapi.fhir</groupId>
+			<artifactId>hapi-fhir-structures-r4</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>ca.uhn.hapi.fhir</groupId>
+			<artifactId>hapi-fhir-converter</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+
+		<!-- OpenAPI/Swagger -->
+		<dependency>
+			<groupId>io.swagger.core.v3</groupId>
+			<artifactId>swagger-models</artifactId>
+			<version>2.1.7</version>
+		</dependency>
+		<dependency>
+			<groupId>io.swagger.core.v3</groupId>
+			<artifactId>swagger-core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.webjars</groupId>
+			<artifactId>swagger-ui</artifactId>
+		</dependency>
+
+		<!-- Thymeleaf -->
+		<dependency>
+			<groupId>org.thymeleaf</groupId>
+			<artifactId>thymeleaf</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.vladsch.flexmark</groupId>
+			<artifactId>flexmark</artifactId>
+		</dependency>
+
+		<!-- Unit Test Deps-->
+		<dependency>
+			<groupId>ca.uhn.hapi.fhir</groupId>
+			<artifactId>hapi-fhir-test-utilities</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>ch.qos.logback</groupId>
+			<artifactId>logback-classic</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.sourceforge.htmlunit</groupId>
+			<artifactId>htmlunit</artifactId>
+			<scope>test</scope>
+		</dependency>
+
+	</dependencies>
+
+</project>
diff --git a/hapi-fhir-server-openapi/src/main/java/ca/uhn/fhir/rest/openapi/OpenApiInterceptor.java b/hapi-fhir-server-openapi/src/main/java/ca/uhn/fhir/rest/openapi/OpenApiInterceptor.java
new file mode 100644
index 00000000000..5d0dfe7c0e3
--- /dev/null
+++ b/hapi-fhir-server-openapi/src/main/java/ca/uhn/fhir/rest/openapi/OpenApiInterceptor.java
@@ -0,0 +1,903 @@
+package ca.uhn.fhir.rest.openapi;
+
+/*-
+ * #%L
+ * hapi-fhir-server-openapi
+ * %%
+ * Copyright (C) 2014 - 2021 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.context.ConfigurationException;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.interceptor.api.Hook;
+import ca.uhn.fhir.interceptor.api.Pointcut;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.server.IServerAddressStrategy;
+import ca.uhn.fhir.rest.server.IServerConformanceProvider;
+import ca.uhn.fhir.rest.server.RestfulServer;
+import ca.uhn.fhir.rest.server.RestfulServerUtils;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
+import ca.uhn.fhir.util.ClasspathUtil;
+import ca.uhn.fhir.util.ExtensionConstants;
+import ca.uhn.fhir.util.HapiExtensions;
+import ca.uhn.fhir.util.UrlUtil;
+import com.vladsch.flexmark.html.HtmlRenderer;
+import com.vladsch.flexmark.parser.Parser;
+import io.swagger.v3.core.util.Yaml;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.PathItem;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.examples.Example;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.media.Content;
+import io.swagger.v3.oas.models.media.MediaType;
+import io.swagger.v3.oas.models.media.ObjectSchema;
+import io.swagger.v3.oas.models.media.Schema;
+import io.swagger.v3.oas.models.parameters.Parameter;
+import io.swagger.v3.oas.models.parameters.RequestBody;
+import io.swagger.v3.oas.models.responses.ApiResponse;
+import io.swagger.v3.oas.models.responses.ApiResponses;
+import io.swagger.v3.oas.models.servers.Server;
+import io.swagger.v3.oas.models.tags.Tag;
+import org.apache.commons.io.IOUtils;
+import org.hl7.fhir.convertors.VersionConvertor_30_40;
+import org.hl7.fhir.convertors.VersionConvertor_40_50;
+import org.hl7.fhir.instance.model.api.IBaseConformance;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
+import org.hl7.fhir.r4.model.CapabilityStatement;
+import org.hl7.fhir.r4.model.CodeableConcept;
+import org.hl7.fhir.r4.model.Coding;
+import org.hl7.fhir.r4.model.DateType;
+import org.hl7.fhir.r4.model.Extension;
+import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.OperationDefinition;
+import org.hl7.fhir.r4.model.Parameters;
+import org.hl7.fhir.r4.model.Reference;
+import org.hl7.fhir.r4.model.Resource;
+import org.hl7.fhir.r4.model.StringType;
+import org.hl7.fhir.r4.model.Type;
+import org.thymeleaf.IEngineConfiguration;
+import org.thymeleaf.TemplateEngine;
+import org.thymeleaf.cache.AlwaysValidCacheEntryValidity;
+import org.thymeleaf.cache.ICacheEntryValidity;
+import org.thymeleaf.cache.NonCacheableCacheEntryValidity;
+import org.thymeleaf.context.IExpressionContext;
+import org.thymeleaf.context.WebContext;
+import org.thymeleaf.linkbuilder.AbstractLinkBuilder;
+import org.thymeleaf.standard.StandardDialect;
+import org.thymeleaf.templatemode.TemplateMode;
+import org.thymeleaf.templateresolver.ITemplateResolver;
+import org.thymeleaf.templateresolver.TemplateResolution;
+import org.thymeleaf.templateresource.ClassLoaderTemplateResource;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
+import static org.apache.commons.lang3.StringUtils.defaultString;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+
+public class OpenApiInterceptor {
+
+	public static final String FHIR_JSON_RESOURCE = "FHIR-JSON-RESOURCE";
+	public static final String FHIR_XML_RESOURCE = "FHIR-XML-RESOURCE";
+	public static final String PAGE_SYSTEM = "System Level Operations";
+	public static final String PAGE_ALL = "All";
+	public static final FhirContext FHIR_CONTEXT_CANONICAL = FhirContext.forR4();
+	public static final String REQUEST_DETAILS = "REQUEST_DETAILS";
+	public static final String RACCOON_PNG = "raccoon.png";
+	private final String mySwaggerUiVersion;
+	private final TemplateEngine myTemplateEngine;
+	private final Parser myFlexmarkParser;
+	private final HtmlRenderer myFlexmarkRenderer;
+	private final Map<String, String> myResourcePathToClasspath = new HashMap<>();
+	private final Map<String, String> myExtensionToContentType = new HashMap<>();
+	private String myBannerImage;
+
+	/**
+	 * Constructor
+	 */
+	public OpenApiInterceptor() {
+		mySwaggerUiVersion = initSwaggerUiWebJar();
+
+		myTemplateEngine = new TemplateEngine();
+		ITemplateResolver resolver = new SwaggerUiTemplateResolver();
+		myTemplateEngine.setTemplateResolver(resolver);
+		StandardDialect dialect = new StandardDialect();
+		myTemplateEngine.setDialect(dialect);
+
+		myTemplateEngine.setLinkBuilder(new TemplateLinkBuilder());
+
+		myFlexmarkParser = Parser.builder().build();
+		myFlexmarkRenderer = HtmlRenderer.builder().build();
+
+		initResources();
+	}
+
+	private void initResources() {
+		setBannerImage(RACCOON_PNG);
+
+		addResourcePathToClasspath("/swagger-ui/index.html", "/ca/uhn/fhir/rest/openapi/index.html");
+		addResourcePathToClasspath("/swagger-ui/" + RACCOON_PNG, "/ca/uhn/fhir/rest/openapi/raccoon.png");
+		addResourcePathToClasspath("/swagger-ui/index.css", "/ca/uhn/fhir/rest/openapi/index.css");
+
+		myExtensionToContentType.put(".png", "image/png");
+		myExtensionToContentType.put(".css", "text/css; charset=UTF-8");
+	}
+
+	protected void addResourcePathToClasspath(String thePath, String theClasspath) {
+		myResourcePathToClasspath.put(thePath, theClasspath);
+	}
+
+	private String initSwaggerUiWebJar() {
+		final String mySwaggerUiVersion;
+		Properties props = new Properties();
+		String resourceName = "/META-INF/maven/org.webjars/swagger-ui/pom.properties";
+		try {
+			InputStream resourceAsStream = ClasspathUtil.loadResourceAsStream(resourceName);
+			props.load(resourceAsStream);
+		} catch (IOException e) {
+			throw new ConfigurationException("Failed to load resource: " + resourceName);
+		}
+		mySwaggerUiVersion = props.getProperty("version");
+		return mySwaggerUiVersion;
+	}
+
+	@Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLER_SELECTED)
+	public boolean serveSwaggerUi(HttpServletRequest theRequest, HttpServletResponse theResponse, ServletRequestDetails theRequestDetails) throws IOException {
+		String requestPath = theRequest.getPathInfo();
+
+		if (isBlank(requestPath) || requestPath.equals("/")) {
+			Set<String> highestRankedAcceptValues = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theRequest);
+			if (highestRankedAcceptValues.contains(Constants.CT_HTML)) {
+				theResponse.sendRedirect("./swagger-ui/");
+				return false;
+			}
+
+		}
+
+		if (requestPath.startsWith("/swagger-ui/")) {
+
+			return !handleResourceRequest(theResponse, theRequestDetails, requestPath);
+
+		} else if (requestPath.equals("/api-docs")) {
+
+			OpenAPI openApi = generateOpenApi(theRequestDetails);
+			String response = Yaml.pretty(openApi);
+
+			theResponse.setContentType("text/yaml");
+			theResponse.setStatus(200);
+			theResponse.getWriter().write(response);
+			theResponse.getWriter().close();
+			return false;
+
+		}
+
+		return true;
+	}
+
+	protected boolean handleResourceRequest(HttpServletResponse theResponse, ServletRequestDetails theRequestDetails, String requestPath) throws IOException {
+		if (requestPath.equals("/swagger-ui/") || requestPath.equals("/swagger-ui/index.html")) {
+			serveSwaggerUiHtml(theRequestDetails, theResponse);
+			return true;
+		}
+
+		String resourceClasspath = myResourcePathToClasspath.get(requestPath);
+		if (resourceClasspath != null) {
+			theResponse.setStatus(200);
+
+			String extension = requestPath.substring(requestPath.lastIndexOf('.'));
+			String contentType = myExtensionToContentType.get(extension);
+			assert contentType != null;
+			theResponse.setContentType(contentType);
+			try (InputStream resource = ClasspathUtil.loadResourceAsStream(resourceClasspath)) {
+				IOUtils.copy(resource, theResponse.getOutputStream());
+				theResponse.getOutputStream().close();
+			}
+			return true;
+		}
+
+
+		String resourcePath = requestPath.substring("/swagger-ui/".length());
+		try (InputStream resource = ClasspathUtil.loadResourceAsStream("/META-INF/resources/webjars/swagger-ui/" + mySwaggerUiVersion + "/" + resourcePath)) {
+
+			if (resourcePath.endsWith(".js") || resourcePath.endsWith(".map")) {
+				theResponse.setContentType("application/javascript");
+				theResponse.setStatus(200);
+				IOUtils.copy(resource, theResponse.getOutputStream());
+				theResponse.getOutputStream().close();
+				return true;
+			}
+
+			if (resourcePath.endsWith(".css")) {
+				theResponse.setContentType("text/css");
+				theResponse.setStatus(200);
+				IOUtils.copy(resource, theResponse.getOutputStream());
+				theResponse.getOutputStream().close();
+				return true;
+			}
+
+		}
+		return false;
+	}
+
+	@SuppressWarnings("unchecked")
+	private void serveSwaggerUiHtml(ServletRequestDetails theRequestDetails, HttpServletResponse theResponse) throws IOException {
+		CapabilityStatement cs = getCapabilityStatement(theRequestDetails);
+
+		theResponse.setStatus(200);
+		theResponse.setContentType(Constants.CT_HTML);
+
+		HttpServletRequest servletRequest = theRequestDetails.getServletRequest();
+		ServletContext servletContext = servletRequest.getServletContext();
+		WebContext context = new WebContext(servletRequest, theResponse, servletContext);
+		context.setVariable(REQUEST_DETAILS, theRequestDetails);
+		context.setVariable("DESCRIPTION", cs.getImplementation().getDescription());
+		context.setVariable("SERVER_NAME", cs.getSoftware().getName());
+		context.setVariable("SERVER_VERSION", cs.getSoftware().getVersion());
+		context.setVariable("BASE_URL", cs.getImplementation().getUrl());
+		context.setVariable("BANNER_IMAGE_URL", getBannerImage());
+		context.setVariable("OPENAPI_DOCS", cs.getImplementation().getUrl() + "/api-docs");
+		context.setVariable("FHIR_VERSION", cs.getFhirVersion().toCode());
+		context.setVariable("FHIR_VERSION_CODENAME", FhirVersionEnum.forVersionString(cs.getFhirVersion().toCode()).name());
+
+		String copyright = cs.getCopyright();
+		if (isNotBlank(copyright)) {
+			copyright = myFlexmarkRenderer.render(myFlexmarkParser.parse(copyright));
+			context.setVariable("COPYRIGHT_HTML", copyright);
+		}
+
+		List<String> pageNames = new ArrayList<>();
+		Map<String, Integer> resourceToCount = new HashMap<>();
+		cs.getRestFirstRep().getResource().stream().forEach(t -> {
+			String type = t.getType();
+			pageNames.add(type);
+			Extension countExtension = t.getExtensionByUrl(ExtensionConstants.CONF_RESOURCE_COUNT);
+			if (countExtension != null) {
+				IPrimitiveType<? extends Number> countExtensionValue = (IPrimitiveType<? extends Number>) countExtension.getValueAsPrimitive();
+				if (countExtensionValue != null && countExtensionValue.hasValue()) {
+					resourceToCount.put(type, countExtensionValue.getValue().intValue());
+				}
+			}
+		});
+		pageNames.sort((o1, o2) -> {
+			Integer count1 = resourceToCount.get(o1);
+			Integer count2 = resourceToCount.get(o2);
+			if (count1 != null && count2 != null) {
+				return count2 - count1;
+			}
+			if (count1 != null) {
+				return -1;
+			}
+			if (count2 != null) {
+				return 1;
+			}
+			return o1.compareTo(o2);
+		});
+
+		pageNames.add(0, PAGE_ALL);
+		pageNames.add(1, PAGE_SYSTEM);
+
+		context.setVariable("PAGE_NAMES", pageNames);
+		context.setVariable("PAGE_NAME_TO_COUNT", resourceToCount);
+
+		String page = extractPageName(theRequestDetails, PAGE_SYSTEM);
+		context.setVariable("PAGE", page);
+
+		String outcome = myTemplateEngine.process("index.html", context);
+
+		theResponse.getWriter().write(outcome);
+		theResponse.getWriter().close();
+	}
+
+	private String extractPageName(ServletRequestDetails theRequestDetails, String theDefault) {
+		String[] pageValues = theRequestDetails.getParameters().get("page");
+		String page = null;
+		if (pageValues != null && pageValues.length > 0) {
+			page = pageValues[0];
+		}
+		if (isBlank(page)) {
+			page = theDefault;
+		}
+		return page;
+	}
+
+	private OpenAPI generateOpenApi(ServletRequestDetails theRequestDetails) {
+		String page = extractPageName(theRequestDetails, null);
+
+		CapabilityStatement cs = getCapabilityStatement(theRequestDetails);
+		FhirContext ctx = theRequestDetails.getFhirContext();
+
+		IServerConformanceProvider<?> capabilitiesProvider = null;
+		RestfulServer restfulServer = theRequestDetails.getServer();
+		if (restfulServer.getServerConformanceProvider() instanceof IServerConformanceProvider) {
+			capabilitiesProvider = (IServerConformanceProvider<?>) restfulServer.getServerConformanceProvider();
+		}
+
+
+		OpenAPI openApi = new OpenAPI();
+
+		openApi.setInfo(new Info());
+		openApi.getInfo().setDescription(cs.getDescription());
+		openApi.getInfo().setTitle(cs.getSoftware().getName());
+		openApi.getInfo().setVersion(cs.getSoftware().getVersion());
+		openApi.getInfo().setContact(new Contact());
+		openApi.getInfo().getContact().setName(cs.getContactFirstRep().getName());
+		openApi.getInfo().getContact().setEmail(cs.getContactFirstRep().getTelecomFirstRep().getValue());
+
+		Server server = new Server();
+		openApi.addServersItem(server);
+		server.setUrl(cs.getImplementation().getUrl());
+		server.setDescription(cs.getSoftware().getName());
+
+		Paths paths = new Paths();
+		openApi.setPaths(paths);
+
+		if (page == null || page.equals(PAGE_SYSTEM) || page.equals(PAGE_ALL)) {
+			Tag serverTag = new Tag();
+			serverTag.setName(PAGE_SYSTEM);
+			serverTag.setDescription("Server-level operations");
+			openApi.addTagsItem(serverTag);
+
+			Operation capabilitiesOperation = getPathItem(paths, "/metadata", PathItem.HttpMethod.GET);
+			capabilitiesOperation.addTagsItem(PAGE_SYSTEM);
+			capabilitiesOperation.setSummary("server-capabilities: Fetch the server FHIR CapabilityStatement");
+			addFhirResourceResponse(ctx, openApi, capabilitiesOperation, "CapabilityStatement");
+
+			Set<CapabilityStatement.SystemRestfulInteraction> systemInteractions = cs.getRestFirstRep().getInteraction().stream().map(t -> t.getCode()).collect(Collectors.toSet());
+
+			// Transaction Operation
+			if (systemInteractions.contains(CapabilityStatement.SystemRestfulInteraction.TRANSACTION) || systemInteractions.contains(CapabilityStatement.SystemRestfulInteraction.BATCH)) {
+				Operation transaction = getPathItem(paths, "/", PathItem.HttpMethod.POST);
+				transaction.addTagsItem(PAGE_SYSTEM);
+				transaction.setSummary("server-transaction: Execute a FHIR Transaction (or FHIR Batch) Bundle");
+				addFhirResourceResponse(ctx, openApi, transaction, null);
+				addFhirResourceRequestBody(openApi, transaction, ctx, null);
+			}
+
+			// System History Operation
+			if (systemInteractions.contains(CapabilityStatement.SystemRestfulInteraction.HISTORYSYSTEM)) {
+				Operation systemHistory = getPathItem(paths, "/_history", PathItem.HttpMethod.GET);
+				systemHistory.addTagsItem(PAGE_SYSTEM);
+				systemHistory.setSummary("server-history: Fetch the resource change history across all resource types on the server");
+				addFhirResourceResponse(ctx, openApi, systemHistory, null);
+			}
+
+			// System-level Operations
+			for (CapabilityStatement.CapabilityStatementRestResourceOperationComponent nextOperation : cs.getRestFirstRep().getOperation()) {
+				addFhirOperation(ctx, openApi, theRequestDetails, capabilitiesProvider, paths, null, nextOperation);
+			}
+
+		}
+
+		for (CapabilityStatement.CapabilityStatementRestResourceComponent nextResource : cs.getRestFirstRep().getResource()) {
+			String resourceType = nextResource.getType();
+
+			if (page != null && !page.equals(resourceType) && !page.equals(PAGE_ALL)) {
+				continue;
+			}
+
+			Set<CapabilityStatement.TypeRestfulInteraction> typeRestfulInteractions = nextResource.getInteraction().stream().map(t -> t.getCodeElement().getValue()).collect(Collectors.toSet());
+
+			Tag resourceTag = new Tag();
+			resourceTag.setName(resourceType);
+			resourceTag.setDescription("The " + resourceType + " FHIR resource type");
+			openApi.addTagsItem(resourceTag);
+
+			// Instance Read
+			if (typeRestfulInteractions.contains(CapabilityStatement.TypeRestfulInteraction.READ)) {
+				Operation operation = getPathItem(paths, "/" + resourceType + "/{id}", PathItem.HttpMethod.GET);
+				operation.addTagsItem(resourceType);
+				operation.setSummary("read-instance: Read " + resourceType + " instance");
+				addResourceIdParameter(operation);
+				addFhirResourceResponse(ctx, openApi, operation, null);
+			}
+
+			// Instance VRead
+			if (typeRestfulInteractions.contains(CapabilityStatement.TypeRestfulInteraction.VREAD)) {
+				Operation operation = getPathItem(paths, "/" + resourceType + "/{id}/_history/{version_id}", PathItem.HttpMethod.GET);
+				operation.addTagsItem(resourceType);
+				operation.setSummary("vread-instance: Read " + resourceType + " instance with specific version");
+				addResourceIdParameter(operation);
+				addResourceVersionIdParameter(operation);
+				addFhirResourceResponse(ctx, openApi, operation, null);
+			}
+
+			// Type Create
+			if (typeRestfulInteractions.contains(CapabilityStatement.TypeRestfulInteraction.CREATE)) {
+				Operation operation = getPathItem(paths, "/" + resourceType, PathItem.HttpMethod.POST);
+				operation.addTagsItem(resourceType);
+				operation.setSummary("create-type: Create a new " + resourceType + " instance");
+				addFhirResourceRequestBody(openApi, operation, ctx, genericExampleSupplier(ctx, resourceType));
+				addFhirResourceResponse(ctx, openApi, operation, null);
+			}
+
+			// Instance Update
+			if (typeRestfulInteractions.contains(CapabilityStatement.TypeRestfulInteraction.UPDATE)) {
+				Operation operation = getPathItem(paths, "/" + resourceType + "/{id}", PathItem.HttpMethod.PUT);
+				operation.addTagsItem(resourceType);
+				operation.setSummary("update-instance: Update an existing " + resourceType + " instance, or create using a client-assigned ID");
+				addResourceIdParameter(operation);
+				addFhirResourceRequestBody(openApi, operation, ctx, genericExampleSupplier(ctx, resourceType));
+				addFhirResourceResponse(ctx, openApi, operation, null);
+			}
+
+			// Type history
+			if (typeRestfulInteractions.contains(CapabilityStatement.TypeRestfulInteraction.HISTORYTYPE)) {
+				Operation operation = getPathItem(paths, "/" + resourceType + "/_history", PathItem.HttpMethod.GET);
+				operation.addTagsItem(resourceType);
+				operation.setSummary("type-history: Fetch the resource change history for all resources of type " + resourceType);
+				addFhirResourceResponse(ctx, openApi, operation, null);
+			}
+
+			// Instance history
+			if (typeRestfulInteractions.contains(CapabilityStatement.TypeRestfulInteraction.HISTORYTYPE)) {
+				Operation operation = getPathItem(paths, "/" + resourceType + "/{id}/_history", PathItem.HttpMethod.GET);
+				operation.addTagsItem(resourceType);
+				operation.setSummary("instance-history: Fetch the resource change history for all resources of type " + resourceType);
+				addResourceIdParameter(operation);
+				addFhirResourceResponse(ctx, openApi, operation, null);
+			}
+
+			// Instance Patch
+			if (typeRestfulInteractions.contains(CapabilityStatement.TypeRestfulInteraction.PATCH)) {
+				Operation operation = getPathItem(paths, "/" + resourceType + "/{id}", PathItem.HttpMethod.PATCH);
+				operation.addTagsItem(resourceType);
+				operation.setSummary("instance-patch: Patch a resource instance of type " + resourceType + " by ID");
+				addResourceIdParameter(operation);
+				addFhirResourceRequestBody(openApi, operation, FHIR_CONTEXT_CANONICAL, patchExampleSupplier());
+				addFhirResourceResponse(ctx, openApi, operation, null);
+			}
+
+			// Instance Delete
+			if (typeRestfulInteractions.contains(CapabilityStatement.TypeRestfulInteraction.DELETE)) {
+				Operation operation = getPathItem(paths, "/" + resourceType + "/{id}", PathItem.HttpMethod.DELETE);
+				operation.addTagsItem(resourceType);
+				operation.setSummary("instance-delete: Perform a logical delete on a resource instance");
+				addResourceIdParameter(operation);
+				addFhirResourceResponse(ctx, openApi, operation, null);
+			}
+
+			// Search
+			if (typeRestfulInteractions.contains(CapabilityStatement.TypeRestfulInteraction.SEARCHTYPE)) {
+				Operation operation = getPathItem(paths, "/" + resourceType, PathItem.HttpMethod.GET);
+				operation.addTagsItem(resourceType);
+				operation.setDescription("This is a search type");
+				operation.setSummary("search-type: Update an existing " + resourceType + " instance, or create using a client-assigned ID");
+				addFhirResourceResponse(ctx, openApi, operation, null);
+
+				for (CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent nextSearchParam : nextResource.getSearchParam()) {
+					Parameter parametersItem = new Parameter();
+					operation.addParametersItem(parametersItem);
+
+					parametersItem.setName(nextSearchParam.getName());
+					parametersItem.setIn("query");
+					parametersItem.setDescription(nextSearchParam.getDocumentation());
+					parametersItem.setStyle(Parameter.StyleEnum.SIMPLE);
+				}
+			}
+
+			// Resource-level Operations
+			for (CapabilityStatement.CapabilityStatementRestResourceOperationComponent nextOperation : nextResource.getOperation()) {
+				addFhirOperation(ctx, openApi, theRequestDetails, capabilitiesProvider, paths, resourceType, nextOperation);
+			}
+
+		}
+
+		return openApi;
+	}
+
+	private Supplier<IBaseResource> patchExampleSupplier() {
+		return () -> {
+			Parameters example = new Parameters();
+			Parameters.ParametersParameterComponent operation = example
+				.addParameter()
+				.setName("operation");
+			operation.addPart().setName("type").setValue(new StringType("add"));
+			operation.addPart().setName("path").setValue(new StringType("Patient"));
+			operation.addPart().setName("name").setValue(new StringType("birthDate"));
+			operation.addPart().setName("value").setValue(new DateType("1930-01-01"));
+			return example;
+		};
+	}
+
+	private void addSchemaFhirResource(OpenAPI theOpenApi) {
+		ensureComponentsSchemasPopulated(theOpenApi);
+
+		if (!theOpenApi.getComponents().getSchemas().containsKey(FHIR_JSON_RESOURCE)) {
+			ObjectSchema fhirJsonSchema = new ObjectSchema();
+			fhirJsonSchema.setDescription("A FHIR resource");
+			theOpenApi.getComponents().addSchemas(FHIR_JSON_RESOURCE, fhirJsonSchema);
+		}
+
+		if (!theOpenApi.getComponents().getSchemas().containsKey(FHIR_XML_RESOURCE)) {
+			ObjectSchema fhirXmlSchema = new ObjectSchema();
+			fhirXmlSchema.setDescription("A FHIR resource");
+			theOpenApi.getComponents().addSchemas(FHIR_XML_RESOURCE, fhirXmlSchema);
+		}
+	}
+
+	private void ensureComponentsSchemasPopulated(OpenAPI theOpenApi) {
+		if (theOpenApi.getComponents() == null) {
+			theOpenApi.setComponents(new Components());
+		}
+		if (theOpenApi.getComponents().getSchemas() == null) {
+			theOpenApi.getComponents().setSchemas(new LinkedHashMap<>());
+		}
+	}
+
+	private CapabilityStatement getCapabilityStatement(ServletRequestDetails theRequestDetails) {
+		RestfulServer restfulServer = theRequestDetails.getServer();
+		IBaseConformance versionIndependentCapabilityStatement = restfulServer.getCapabilityStatement(theRequestDetails);
+		return toCanonicalVersion(versionIndependentCapabilityStatement);
+	}
+
+	private void addFhirOperation(FhirContext theFhirContext, OpenAPI theOpenApi, ServletRequestDetails theRequestDetails, IServerConformanceProvider<?> theCapabilitiesProvider, Paths thePaths, String theResourceType, CapabilityStatement.CapabilityStatementRestResourceOperationComponent theOperation) {
+		if (theCapabilitiesProvider != null) {
+			IdType definitionId = new IdType(theOperation.getDefinition());
+			IBaseResource operationDefinitionNonCanonical = theCapabilitiesProvider.readOperationDefinition(definitionId, theRequestDetails);
+			if (operationDefinitionNonCanonical == null) {
+				return;
+			}
+
+			OperationDefinition operationDefinition = toCanonicalVersion(operationDefinitionNonCanonical);
+
+			if (!operationDefinition.getAffectsState()) {
+
+				// GET form for non-state-affecting operations
+				if (theResourceType != null) {
+					if (operationDefinition.getType()) {
+						Operation operation = getPathItem(thePaths, "/" + theResourceType + "/$" + operationDefinition.getCode(), PathItem.HttpMethod.GET);
+						populateOperation(theFhirContext, theOpenApi, theResourceType, operationDefinition, operation, true);
+					}
+					if (operationDefinition.getInstance()) {
+						Operation operation = getPathItem(thePaths, "/" + theResourceType + "/{id}/$" + operationDefinition.getCode(), PathItem.HttpMethod.GET);
+						addResourceIdParameter(operation);
+						populateOperation(theFhirContext, theOpenApi, theResourceType, operationDefinition, operation, true);
+					}
+				} else {
+					if (operationDefinition.getSystem()) {
+						Operation operation = getPathItem(thePaths, "/$" + operationDefinition.getCode(), PathItem.HttpMethod.GET);
+						populateOperation(theFhirContext, theOpenApi, null, operationDefinition, operation, true);
+					}
+				}
+
+			} else {
+
+				// POST form for all operations
+				if (theResourceType != null) {
+					if (operationDefinition.getType()) {
+						Operation operation = getPathItem(thePaths, "/" + theResourceType + "/$" + operationDefinition.getCode(), PathItem.HttpMethod.POST);
+						populateOperation(theFhirContext, theOpenApi, theResourceType, operationDefinition, operation, false);
+					}
+					if (operationDefinition.getInstance()) {
+						Operation operation = getPathItem(thePaths, "/" + theResourceType + "/{id}/$" + operationDefinition.getCode(), PathItem.HttpMethod.POST);
+						addResourceIdParameter(operation);
+						populateOperation(theFhirContext, theOpenApi, theResourceType, operationDefinition, operation, false);
+					}
+				} else {
+					if (operationDefinition.getSystem()) {
+						Operation operation = getPathItem(thePaths, "/$" + operationDefinition.getCode(), PathItem.HttpMethod.POST);
+						populateOperation(theFhirContext, theOpenApi, null, operationDefinition, operation, false);
+					}
+				}
+
+			}
+		}
+	}
+
+	private void populateOperation(FhirContext theFhirContext, OpenAPI theOpenApi, String theResourceType, OperationDefinition theOperationDefinition, Operation theOperation, boolean theGet) {
+		if (theResourceType == null) {
+			theOperation.addTagsItem(PAGE_SYSTEM);
+		} else {
+			theOperation.addTagsItem(theResourceType);
+		}
+		theOperation.setSummary(theOperationDefinition.getTitle());
+		theOperation.setDescription(theOperationDefinition.getDescription());
+		addFhirResourceResponse(theFhirContext, theOpenApi, theOperation, null);
+
+		if (theGet) {
+
+			for (OperationDefinition.OperationDefinitionParameterComponent nextParameter : theOperationDefinition.getParameter()) {
+				Parameter parametersItem = new Parameter();
+				theOperation.addParametersItem(parametersItem);
+
+				parametersItem.setName(nextParameter.getName());
+				parametersItem.setIn("query");
+				parametersItem.setDescription(nextParameter.getDocumentation());
+				parametersItem.setStyle(Parameter.StyleEnum.SIMPLE);
+				parametersItem.setRequired(nextParameter.getMin() > 0);
+
+				List<Extension> exampleExtensions = nextParameter.getExtensionsByUrl(HapiExtensions.EXT_OP_PARAMETER_EXAMPLE_VALUE);
+				if (exampleExtensions.size() == 1) {
+					parametersItem.setExample(exampleExtensions.get(0).getValueAsPrimitive().getValueAsString());
+				} else if (exampleExtensions.size() > 1) {
+					for (Extension next : exampleExtensions) {
+						String nextExample = next.getValueAsPrimitive().getValueAsString();
+						parametersItem.addExample(nextExample, new Example().value(nextExample));
+					}
+				}
+
+			}
+
+		} else {
+
+			Parameters exampleRequestBody = new Parameters();
+			for (OperationDefinition.OperationDefinitionParameterComponent nextSearchParam : theOperationDefinition.getParameter()) {
+				Parameters.ParametersParameterComponent param = exampleRequestBody.addParameter();
+				param.setName(nextSearchParam.getName());
+				String paramType = nextSearchParam.getType();
+				switch (defaultString(paramType)) {
+					case "uri":
+					case "url":
+					case "code":
+					case "string": {
+						IPrimitiveType<?> type = (IPrimitiveType<?>) FHIR_CONTEXT_CANONICAL.getElementDefinition(paramType).newInstance();
+						type.setValueAsString("example");
+						param.setValue((Type) type);
+						break;
+					}
+					case "integer": {
+						IPrimitiveType<?> type = (IPrimitiveType<?>) FHIR_CONTEXT_CANONICAL.getElementDefinition(paramType).newInstance();
+						type.setValueAsString("0");
+						param.setValue((Type) type);
+						break;
+					}
+					case "boolean": {
+						IPrimitiveType<?> type = (IPrimitiveType<?>) FHIR_CONTEXT_CANONICAL.getElementDefinition(paramType).newInstance();
+						type.setValueAsString("false");
+						param.setValue((Type) type);
+						break;
+					}
+					case "CodeableConcept": {
+						CodeableConcept type = new CodeableConcept();
+						type.getCodingFirstRep().setSystem("http://example.com");
+						type.getCodingFirstRep().setCode("1234");
+						param.setValue(type);
+						break;
+					}
+					case "Coding": {
+						Coding type = new Coding();
+						type.setSystem("http://example.com");
+						type.setCode("1234");
+						param.setValue(type);
+						break;
+					}
+					case "Reference":
+						Reference reference = new Reference("example");
+						param.setValue(reference);
+						break;
+					case "Resource":
+						if (theResourceType != null) {
+							IBaseResource resource = FHIR_CONTEXT_CANONICAL.getResourceDefinition(theResourceType).newInstance();
+							resource.setId("1");
+							param.setResource((Resource) resource);
+						}
+						break;
+				}
+
+			}
+
+			String exampleRequestBodyString = FHIR_CONTEXT_CANONICAL.newJsonParser().setPrettyPrint(true).encodeResourceToString(exampleRequestBody);
+			theOperation.setRequestBody(new RequestBody());
+			theOperation.getRequestBody().setContent(new Content());
+			MediaType mediaType = new MediaType();
+			mediaType.setExample(exampleRequestBodyString);
+			mediaType.setSchema(new Schema().type("object").title("FHIR Resource"));
+			theOperation.getRequestBody().getContent().addMediaType(Constants.CT_FHIR_JSON_NEW, mediaType);
+
+
+		}
+	}
+
+	private Operation getPathItem(Paths thePaths, String thePath, PathItem.HttpMethod theMethod) {
+		PathItem pathItem;
+		if (thePaths.containsKey(thePath)) {
+			pathItem = thePaths.get(thePath);
+		} else {
+			pathItem = new PathItem();
+			thePaths.addPathItem(thePath, pathItem);
+		}
+
+		switch (theMethod) {
+			case POST:
+				assert pathItem.getPost() == null : "Have duplicate POST at path: " + thePath;
+				return pathItem.post(new Operation()).getPost();
+			case GET:
+				assert pathItem.getGet() == null : "Have duplicate GET at path: " + thePath;
+				return pathItem.get(new Operation()).getGet();
+			case PUT:
+				assert pathItem.getPut() == null;
+				return pathItem.put(new Operation()).getPut();
+			case PATCH:
+				assert pathItem.getPatch() == null;
+				return pathItem.patch(new Operation()).getPatch();
+			case DELETE:
+				assert pathItem.getDelete() == null;
+				return pathItem.delete(new Operation()).getDelete();
+			case HEAD:
+			case OPTIONS:
+			case TRACE:
+			default:
+				throw new IllegalStateException();
+		}
+	}
+
+	private void addFhirResourceRequestBody(OpenAPI theOpenApi, Operation theOperation, FhirContext theExampleFhirContext, Supplier<IBaseResource> theExampleSupplier) {
+		RequestBody requestBody = new RequestBody();
+		requestBody.setContent(provideContentFhirResource(theOpenApi, theExampleFhirContext, theExampleSupplier));
+		theOperation.setRequestBody(requestBody);
+	}
+
+	private void addResourceVersionIdParameter(Operation theOperation) {
+		Parameter parameter = new Parameter();
+		parameter.setName("version_id");
+		parameter.setIn("path");
+		parameter.setDescription("The resource version ID");
+		parameter.setExample("1");
+		parameter.setSchema(new Schema().type("string").minimum(new BigDecimal(1)));
+		parameter.setStyle(Parameter.StyleEnum.SIMPLE);
+		theOperation.addParametersItem(parameter);
+	}
+
+	private void addFhirResourceResponse(FhirContext theFhirContext, OpenAPI theOpenApi, Operation theOperation, String theResourceType) {
+		theOperation.setResponses(new ApiResponses());
+		ApiResponse response200 = new ApiResponse();
+		response200.setDescription("Success");
+		response200.setContent(provideContentFhirResource(theOpenApi, theFhirContext, genericExampleSupplier(theFhirContext, theResourceType)));
+		theOperation.getResponses().addApiResponse("200", response200);
+	}
+
+	private Supplier<IBaseResource> genericExampleSupplier(FhirContext theFhirContext, String theResourceType) {
+		if (theResourceType == null) {
+			return null;
+		}
+		return () -> {
+			IBaseResource example = null;
+			if (theResourceType != null) {
+				example = theFhirContext.getResourceDefinition(theResourceType).newInstance();
+			}
+			return example;
+		};
+	}
+
+
+	private Content provideContentFhirResource(OpenAPI theOpenApi, FhirContext theExampleFhirContext, Supplier<IBaseResource> theExampleSupplier) {
+		addSchemaFhirResource(theOpenApi);
+		Content retVal = new Content();
+
+		MediaType jsonSchema = new MediaType().schema(new ObjectSchema().$ref("#/components/schemas/" + FHIR_JSON_RESOURCE));
+		if (theExampleSupplier != null) {
+			jsonSchema.setExample(theExampleFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(theExampleSupplier.get()));
+		}
+		retVal.addMediaType(Constants.CT_FHIR_JSON_NEW, jsonSchema);
+
+		MediaType xmlSchema = new MediaType().schema(new ObjectSchema().$ref("#/components/schemas/" + FHIR_XML_RESOURCE));
+		if (theExampleSupplier != null) {
+			xmlSchema.setExample(theExampleFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(theExampleSupplier.get()));
+		}
+		retVal.addMediaType(Constants.CT_FHIR_XML_NEW, xmlSchema);
+		return retVal;
+	}
+
+	private void addResourceIdParameter(Operation theOperation) {
+		Parameter parameter = new Parameter();
+		parameter.setName("id");
+		parameter.setIn("path");
+		parameter.setDescription("The resource ID");
+		parameter.setExample("123");
+		parameter.setSchema(new Schema().type("string").minimum(new BigDecimal(1)));
+		parameter.setStyle(Parameter.StyleEnum.SIMPLE);
+		theOperation.addParametersItem(parameter);
+	}
+
+	protected ClassLoaderTemplateResource getIndexTemplate() {
+		return new ClassLoaderTemplateResource(myResourcePathToClasspath.get("/swagger-ui/index.html"), StandardCharsets.UTF_8.name());
+	}
+
+	public void setBannerImage(String theBannerImage) {
+		myBannerImage = theBannerImage;
+	}
+
+	public String getBannerImage() {
+		return myBannerImage;
+	}
+
+	private class SwaggerUiTemplateResolver implements ITemplateResolver {
+		@Override
+		public String getName() {
+			return getClass().getName();
+		}
+
+		@Override
+		public Integer getOrder() {
+			return 0;
+		}
+
+		@Override
+		public TemplateResolution resolveTemplate(IEngineConfiguration configuration, String ownerTemplate, String template, Map<String, Object> templateResolutionAttributes) {
+			ClassLoaderTemplateResource resource = getIndexTemplate();
+			ICacheEntryValidity cacheValidity = new AlwaysValidCacheEntryValidity();
+			return new TemplateResolution(resource, TemplateMode.HTML, cacheValidity);
+		}
+	}
+
+	private static class TemplateLinkBuilder extends AbstractLinkBuilder {
+
+		@Override
+		public String buildLink(IExpressionContext theExpressionContext, String theBase, Map<String, Object> theParameters) {
+
+			ServletRequestDetails requestDetails = (ServletRequestDetails) theExpressionContext.getVariable(REQUEST_DETAILS);
+
+			IServerAddressStrategy addressStrategy = requestDetails.getServer().getServerAddressStrategy();
+			String baseUrl = addressStrategy.determineServerBase(requestDetails.getServletRequest().getServletContext(), requestDetails.getServletRequest());
+
+			StringBuilder builder = new StringBuilder();
+			builder.append(baseUrl);
+			builder.append(theBase);
+			if (!theParameters.isEmpty()) {
+				builder.append("?");
+				for (Iterator<Map.Entry<String, Object>> iter = theParameters.entrySet().iterator(); iter.hasNext(); ) {
+					Map.Entry<String, Object> nextEntry = iter.next();
+					builder.append(UrlUtil.escapeUrlParam(nextEntry.getKey()));
+					builder.append("=");
+					builder.append(UrlUtil.escapeUrlParam(defaultIfNull(nextEntry.getValue(), "").toString()));
+					if (iter.hasNext()) {
+						builder.append("&");
+					}
+				}
+			}
+
+			return builder.toString();
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	private static <T extends Resource> T toCanonicalVersion(IBaseResource theNonCanonical) {
+		IBaseResource canonical;
+		if (theNonCanonical instanceof org.hl7.fhir.dstu3.model.Resource) {
+			canonical = VersionConvertor_30_40.convertResource((org.hl7.fhir.dstu3.model.Resource) theNonCanonical, true);
+		} else if (theNonCanonical instanceof org.hl7.fhir.r5.model.Resource) {
+			canonical = VersionConvertor_40_50.convertResource((org.hl7.fhir.r5.model.Resource) theNonCanonical);
+		} else {
+			canonical = theNonCanonical;
+		}
+		return (T) canonical;
+	}
+
+
+}
diff --git a/hapi-fhir-server-openapi/src/main/resources/ca/uhn/fhir/rest/openapi/index.css b/hapi-fhir-server-openapi/src/main/resources/ca/uhn/fhir/rest/openapi/index.css
new file mode 100644
index 00000000000..a94d230ed3c
--- /dev/null
+++ b/hapi-fhir-server-openapi/src/main/resources/ca/uhn/fhir/rest/openapi/index.css
@@ -0,0 +1,141 @@
+html
+{
+    box-sizing: border-box;
+    overflow: -moz-scrollbars-vertical;
+    overflow-y: scroll;
+}
+
+*,
+*:before,
+*:after
+{
+    box-sizing: inherit;
+}
+
+body
+{
+    margin:0;
+    background: #fafafa;
+}
+
+.scheme-container, .information-container
+{
+    display: none
+}
+
+.banner {
+    padding-top: 20px;
+    padding-left: 25px;
+    display: flex;
+    flex-direction: row;
+    background-color: #AAA;
+    border-bottom: 2px solid #888;
+}
+
+.banner H1 {
+    font-family: sans-serif;
+    height: 150%;
+    position: relative;
+    line-height: 120%;
+    top: 6px;
+    left: 18px;
+}
+
+.banner .version {
+    font-size: 30%;
+    position: relative;
+    top: -11px;
+    background-color: #888;
+    color: #FFF;
+    padding: 5px;
+    border-radius: 15px;
+}
+
+.bannerCopyright {
+    font-family: sans-serif;
+    padding-top: 20px;
+    padding-left: 30px;
+    padding-right: 30px;
+    padding-bottom: 20px;
+    background-color: #CCC;
+    border-bottom: 2px solid #888;
+    width: 100%;
+    font-size: 0.9em;
+}
+
+.banner2 {
+    font-family: sans-serif;
+    padding-top: 30px;
+    padding-left: 30px;
+    padding-right: 30px;
+    padding-bottom: 20px;
+    display: table;
+    flex-direction: row;
+    background-color: #CCC;
+    border-bottom: 2px solid #888;
+    width: 100%;
+}
+
+.banner2 > DIV {
+    display: table-row;
+}
+
+.banner2 > DIV > DIV {
+    display: table-cell;
+    padding-bottom: 10px;
+}
+
+.banner2_key {
+    min-width: 100px;
+    white-space: nowrap;
+    font-weight: bold;
+}
+
+.banner2_value {
+    padding-left: 20px;
+    width: 100%;
+}
+
+.banner3 {
+    font-family: sans-serif;
+    padding-top: 20px;
+    padding-left: 30px;
+    padding-right: 30px;
+    padding-bottom: 20px;
+    background-color: #EEE;
+    border-bottom: 2px solid #888;
+    width: 100%;
+}
+
+.pageButtons {
+    display: flex;
+    flex-wrap: wrap;
+}
+
+.pageButton {
+    background-color: #FFF;
+    color: #666;
+    padding: 10px;
+    margin: 5px;
+    border-radius: 8px;
+    text-decoration: none;
+    line-height: 0.5em;
+    border: 1px solid #FFF;
+}
+
+.pageButton:HOVER {
+    border: 1px solid #888;
+}
+
+.pageButtonSelected {
+    background-color: #888;
+    color: #FFF;
+}
+
+.resourceCountBadge {
+    font-size: 0.8em;
+    background: #DDD;
+    padding: 4px;
+    border-radius: 6px;
+    color: #000;
+}
diff --git a/hapi-fhir-server-openapi/src/main/resources/ca/uhn/fhir/rest/openapi/index.html b/hapi-fhir-server-openapi/src/main/resources/ca/uhn/fhir/rest/openapi/index.html
new file mode 100644
index 00000000000..029918433e3
--- /dev/null
+++ b/hapi-fhir-server-openapi/src/main/resources/ca/uhn/fhir/rest/openapi/index.html
@@ -0,0 +1,66 @@
+<html lang="en" xmlns:th="http://www.thymeleaf.org">
+<head>
+    <meta charset="UTF-8">
+    <title>Swagger UI</title>
+    <link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
+    <link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
+    <link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
+    <link rel="stylesheet" type="text/css" href="./index.css" />
+</head>
+
+<body>
+<div class="container">
+</div>
+<div class="banner">
+    <img th:src="${BANNER_IMAGE_URL}" style="height: 100px;"/>
+    <h1>[[${DESCRIPTION}]]<br/><span class="version" th:text="${SERVER_NAME} + ' ' + ${SERVER_VERSION}"></span></h1>
+</div>
+<div class="banner2">
+    <div>
+        <div class="banner2_key">FHIR Server Base URL</div><div class="banner2_value"><a th:href="${BASE_URL}" th:text="${BASE_URL}"></a></div>
+    </div>
+    <div>
+        <div class="banner2_key">OpenAPI Docs</div><div class="banner2_value"><a th:href="${OPENAPI_DOCS}" th:text="${OPENAPI_DOCS}"></a></div>
+    </div>
+    <div>
+        <div class="banner2_key">FHIR Version</div><div class="banner2_value">[[${FHIR_VERSION}]] ([[${FHIR_VERSION_CODENAME}]])</div>
+    </div>
+</div>
+<div class="bannerCopyright" th:if="${COPYRIGHT_HTML} != null" th:utext="${COPYRIGHT_HTML}">
+</div>
+<div class="banner3">
+    <div class="pageButtons" id="pageButtons">
+        <a class="pageButton" th:each="pageName : ${PAGE_NAMES}" th:classappend="${pageName} == ${PAGE} ? 'pageButtonSelected' : ''" th:href="@{/swagger-ui/(page=${pageName})}">
+            [[${pageName}]]
+            <span th:if="${PAGE_NAME_TO_COUNT.containsKey(pageName)}" th:text="${PAGE_NAME_TO_COUNT.get(pageName)}" class="resourceCountBadge"></span>
+        </a>
+    </div>
+
+</div>
+<div id="swagger-ui"></div>
+
+<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
+<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
+<script>
+    window.onload = function() {
+        // Begin Swagger UI call region
+        const ui = SwaggerUIBundle({
+            url: "[[@{/api-docs(page=${PAGE})}]]",
+            dom_id: '#swagger-ui',
+            deepLinking: true,
+            presets: [
+                SwaggerUIBundle.presets.apis,
+                SwaggerUIStandalonePreset
+            ],
+            plugins: [
+                // SwaggerUIBundle.plugins.DownloadUrl
+            ],
+            // layout: "StandaloneLayout"
+        });
+        // End Swagger UI call region
+
+        window.ui = ui;
+    };
+</script>
+</body>
+</html>
diff --git a/hapi-fhir-server-openapi/src/main/resources/ca/uhn/fhir/rest/openapi/raccoon.png b/hapi-fhir-server-openapi/src/main/resources/ca/uhn/fhir/rest/openapi/raccoon.png
new file mode 100644
index 00000000000..cf043960680
Binary files /dev/null and b/hapi-fhir-server-openapi/src/main/resources/ca/uhn/fhir/rest/openapi/raccoon.png differ
diff --git a/hapi-fhir-server-openapi/src/test/java/ca/uhn/fhir/rest/openapi/OpenApiInterceptorTest.java b/hapi-fhir-server-openapi/src/test/java/ca/uhn/fhir/rest/openapi/OpenApiInterceptorTest.java
new file mode 100644
index 00000000000..cf113b46703
--- /dev/null
+++ b/hapi-fhir-server-openapi/src/test/java/ca/uhn/fhir/rest/openapi/OpenApiInterceptorTest.java
@@ -0,0 +1,274 @@
+package ca.uhn.fhir.rest.openapi;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.interceptor.api.Hook;
+import ca.uhn.fhir.interceptor.api.Pointcut;
+import ca.uhn.fhir.model.api.annotation.Description;
+import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Operation;
+import ca.uhn.fhir.rest.annotation.OperationParam;
+import ca.uhn.fhir.rest.annotation.Patch;
+import ca.uhn.fhir.rest.annotation.ResourceParam;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.PatchTypeEnum;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
+import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
+import ca.uhn.fhir.test.utilities.HtmlUtil;
+import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
+import ca.uhn.fhir.util.ExtensionConstants;
+import com.gargoylesoftware.htmlunit.html.DomElement;
+import com.gargoylesoftware.htmlunit.html.HtmlDivision;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import io.swagger.v3.core.util.Yaml;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.PathItem;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.hamcrest.Matchers;
+import org.hl7.fhir.instance.model.api.IBaseBundle;
+import org.hl7.fhir.instance.model.api.IBaseCoding;
+import org.hl7.fhir.instance.model.api.IBaseConformance;
+import org.hl7.fhir.instance.model.api.IBaseParameters;
+import org.hl7.fhir.instance.model.api.IBaseReference;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
+import org.hl7.fhir.r4.model.CapabilityStatement;
+import org.hl7.fhir.r4.model.DecimalType;
+import org.hl7.fhir.r4.model.Observation;
+import org.hl7.fhir.r4.model.Patient;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class OpenApiInterceptorTest {
+
+	private static final Logger ourLog = LoggerFactory.getLogger(OpenApiInterceptorTest.class);
+	private FhirContext myFhirContext = FhirContext.forCached(FhirVersionEnum.R4);
+	@RegisterExtension
+	@Order(0)
+	protected RestfulServerExtension myServer = new RestfulServerExtension(myFhirContext)
+		.withServletPath("/fhir/*")
+		.withServer(t -> t.registerProvider(new HashMapResourceProvider<>(myFhirContext, Patient.class)))
+		.withServer(t -> t.registerProvider(new HashMapResourceProvider<>(myFhirContext, Observation.class)))
+		.withServer(t -> t.registerProvider(new MyLastNProvider()))
+		.withServer(t -> t.registerInterceptor(new ResponseHighlighterInterceptor()));
+	private CloseableHttpClient myClient;
+
+	@BeforeEach
+	public void before() {
+		myClient = HttpClientBuilder.create().build();
+	}
+
+	@AfterEach
+	public void after() throws IOException {
+		myClient.close();
+		myServer.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
+	}
+
+	@Test
+	public void testFetchSwagger() throws IOException {
+		myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
+
+		String resp;
+		HttpGet get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/metadata?_pretty=true");
+		try (CloseableHttpResponse response = myClient.execute(get)) {
+			resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+			ourLog.info("CapabilityStatement: {}", resp);
+		}
+
+		get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/api-docs");
+		try (CloseableHttpResponse response = myClient.execute(get)) {
+			resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+			ourLog.info("Response: {}", response.getStatusLine());
+			ourLog.info("Response: {}", resp);
+		}
+
+		OpenAPI parsed = Yaml.mapper().readValue(resp, OpenAPI.class);
+
+		PathItem fooOpPath = parsed.getPaths().get("/$foo-op");
+		assertNull(fooOpPath.getGet());
+		assertNotNull(fooOpPath.getPost());
+		assertEquals("Foo Op Description", fooOpPath.getPost().getDescription());
+		assertEquals("Foo Op Short", fooOpPath.getPost().getSummary());
+
+		PathItem lastNPath = parsed.getPaths().get("/Observation/$lastn");
+		assertNull(lastNPath.getPost());
+		assertNotNull(lastNPath.getGet());
+		assertEquals("LastN Description", lastNPath.getGet().getDescription());
+		assertEquals("LastN Short", lastNPath.getGet().getSummary());
+		assertEquals(4, lastNPath.getGet().getParameters().size());
+		assertEquals("Subject description", lastNPath.getGet().getParameters().get(0).getDescription());
+	}
+
+	@Test
+	public void testRedirectFromBaseUrl() throws IOException {
+		myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
+
+		HttpGet get;
+
+		get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/");
+		try (CloseableHttpResponse response = myClient.execute(get)) {
+			assertEquals(400, response.getStatusLine().getStatusCode());
+		}
+
+		get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/");
+		get.addHeader(Constants.HEADER_ACCEPT, Constants.CT_HTML);
+		try (CloseableHttpResponse response = myClient.execute(get)) {
+			String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+			ourLog.info("Response: {}", response);
+			ourLog.info("Response: {}", responseString);
+			assertEquals(200, response.getStatusLine().getStatusCode());
+			assertThat(responseString, containsString("<title>Swagger UI</title>"));
+		}
+
+	}
+
+
+	@Test
+	public void testSwaggerUiWithResourceCounts() throws IOException {
+		myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
+		myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
+
+		String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
+		String resp = fetchSwaggerUi(url);
+		List<String> buttonTexts = parsePageButtonTexts(resp, url);
+		assertThat(buttonTexts.toString(), buttonTexts, Matchers.contains("All", "System Level Operations", "Patient 2", "OperationDefinition 1", "Observation 0"));
+	}
+
+	@Test
+	public void testSwaggerUiWithCopyright() throws IOException {
+		myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor());
+		myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
+
+		String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
+		String resp = fetchSwaggerUi(url);
+		assertThat(resp, resp, containsString("<p>This server is copyright <strong>Example Org</strong> 2021</p>"));
+	}
+
+	@Test
+	public void testSwaggerUiWithResourceCounts_OneResourceOnly() throws IOException {
+		myServer.getRestfulServer().registerInterceptor(new AddResourceCountsInterceptor("OperationDefinition"));
+		myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
+
+		String url = "http://localhost:" + myServer.getPort() + "/fhir/swagger-ui/";
+		String resp = fetchSwaggerUi(url);
+		List<String> buttonTexts = parsePageButtonTexts(resp, url);
+		assertThat(buttonTexts.toString(), buttonTexts, Matchers.contains("All", "System Level Operations", "OperationDefinition 1", "Observation", "Patient"));
+	}
+
+	private String fetchSwaggerUi(String url) throws IOException {
+		String resp;
+		HttpGet get = new HttpGet(url);
+		try (CloseableHttpResponse response = myClient.execute(get)) {
+			resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+			ourLog.info("Response: {}", response.getStatusLine());
+			ourLog.info("Response: {}", resp);
+		}
+		return resp;
+	}
+
+	private List<String> parsePageButtonTexts(String resp, String url) throws IOException {
+		HtmlPage html = HtmlUtil.parseAsHtml(resp, new URL(url));
+		HtmlDivision pageButtons = (HtmlDivision) html.getElementById("pageButtons");
+		List<String> buttonTexts = new ArrayList<>();
+		for (DomElement next : pageButtons.getChildElements()) {
+			buttonTexts.add(next.asNormalizedText());
+		}
+		return buttonTexts;
+	}
+
+
+	public static class AddResourceCountsInterceptor {
+
+		private final HashSet<String> myResourceNamesToAddTo;
+
+		public AddResourceCountsInterceptor(String... theResourceNamesToAddTo) {
+			myResourceNamesToAddTo = new HashSet<>(Arrays.asList(theResourceNamesToAddTo));
+		}
+
+		@Hook(Pointcut.SERVER_CAPABILITY_STATEMENT_GENERATED)
+		public void capabilityStatementGenerated(IBaseConformance theCapabilityStatement) {
+			CapabilityStatement cs = (CapabilityStatement) theCapabilityStatement;
+			cs.setCopyright("This server is copyright **Example Org** 2021");
+
+			int numResources = cs.getRestFirstRep().getResource().size();
+			for (int i = 0; i < numResources; i++) {
+
+				CapabilityStatement.CapabilityStatementRestResourceComponent restResource = cs.getRestFirstRep().getResource().get(i);
+				if (!myResourceNamesToAddTo.isEmpty() && !myResourceNamesToAddTo.contains(restResource.getType())) {
+					continue;
+				}
+
+				restResource.addExtension(
+					ExtensionConstants.CONF_RESOURCE_COUNT,
+					new DecimalType(i) // reverse order
+				);
+
+			}
+		}
+
+	}
+
+	public static class MyLastNProvider {
+
+
+		@Description(value = "LastN Description", shortDefinition = "LastN Short")
+		@Operation(name = Constants.OPERATION_LASTN, typeName = "Observation", idempotent = true)
+		public IBaseBundle lastN(
+			@Description(value = "Subject description", shortDefinition = "Subject short", example = {"Patient/456", "Patient/789"})
+			@OperationParam(name = "subject", typeName = "reference", min = 0, max = 1) IBaseReference theSubject,
+			@OperationParam(name = "category", typeName = "coding", min = 0, max = OperationParam.MAX_UNLIMITED) List<IBaseCoding> theCategories,
+			@OperationParam(name = "code", typeName = "coding", min = 0, max = OperationParam.MAX_UNLIMITED) List<IBaseCoding> theCodes,
+			@OperationParam(name = "max", typeName = "integer", min = 0, max = 1) IPrimitiveType<Integer> theMax
+		) {
+			throw new IllegalStateException();
+		}
+
+		@Description(value = "Foo Op Description", shortDefinition = "Foo Op Short")
+		@Operation(name = "foo-op", idempotent = false)
+		public IBaseBundle foo(
+			ServletRequestDetails theRequestDetails,
+			@Description(shortDefinition = "Reference description", example = "Patient/123")
+			@OperationParam(name = "subject", typeName = "reference", min = 0, max = 1) IBaseReference theSubject,
+			@OperationParam(name = "category", typeName = "coding", min = 0, max = OperationParam.MAX_UNLIMITED) List<IBaseCoding> theCategories,
+			@OperationParam(name = "code", typeName = "coding", min = 0, max = OperationParam.MAX_UNLIMITED) List<IBaseCoding> theCodes,
+			@OperationParam(name = "max", typeName = "integer", min = 0, max = 1) IPrimitiveType<Integer> theMax
+		) {
+			throw new IllegalStateException();
+		}
+
+		@Patch(type = Patient.class)
+		public MethodOutcome patch(HttpServletRequest theRequest, @IdParam IIdType theId, @ConditionalUrlParam String theConditionalUrl, RequestDetails theRequestDetails, @ResourceParam String theBody, PatchTypeEnum thePatchType, @ResourceParam IBaseParameters theRequestBody) {
+			throw new IllegalStateException();
+		}
+
+
+	}
+}
diff --git a/hapi-fhir-server-openapi/src/test/java/ca/uhn/fhir/rest/openapi/OpenApiInterceptorWithAuthorizationInterceptorTest.java b/hapi-fhir-server-openapi/src/test/java/ca/uhn/fhir/rest/openapi/OpenApiInterceptorWithAuthorizationInterceptorTest.java
new file mode 100644
index 00000000000..766e79e5d89
--- /dev/null
+++ b/hapi-fhir-server-openapi/src/test/java/ca/uhn/fhir/rest/openapi/OpenApiInterceptorWithAuthorizationInterceptorTest.java
@@ -0,0 +1,129 @@
+package ca.uhn.fhir.rest.openapi;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.interceptor.api.Hook;
+import ca.uhn.fhir.interceptor.api.Pointcut;
+import ca.uhn.fhir.model.api.annotation.Description;
+import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Operation;
+import ca.uhn.fhir.rest.annotation.OperationParam;
+import ca.uhn.fhir.rest.annotation.Patch;
+import ca.uhn.fhir.rest.annotation.ResourceParam;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.PatchTypeEnum;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
+import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
+import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule;
+import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
+import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
+import ca.uhn.fhir.test.utilities.HtmlUtil;
+import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
+import ca.uhn.fhir.util.ExtensionConstants;
+import com.gargoylesoftware.htmlunit.html.DomElement;
+import com.gargoylesoftware.htmlunit.html.HtmlDivision;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import io.swagger.v3.core.util.Yaml;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.PathItem;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.hamcrest.Matchers;
+import org.hl7.fhir.instance.model.api.IBaseBundle;
+import org.hl7.fhir.instance.model.api.IBaseCoding;
+import org.hl7.fhir.instance.model.api.IBaseConformance;
+import org.hl7.fhir.instance.model.api.IBaseParameters;
+import org.hl7.fhir.instance.model.api.IBaseReference;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
+import org.hl7.fhir.r4.model.CapabilityStatement;
+import org.hl7.fhir.r4.model.DecimalType;
+import org.hl7.fhir.r4.model.Observation;
+import org.hl7.fhir.r4.model.Patient;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class OpenApiInterceptorWithAuthorizationInterceptorTest {
+
+	private static final Logger ourLog = LoggerFactory.getLogger(OpenApiInterceptorWithAuthorizationInterceptorTest.class);
+	private FhirContext myFhirContext = FhirContext.forCached(FhirVersionEnum.R4);
+	@RegisterExtension
+	@Order(0)
+	protected RestfulServerExtension myServer = new RestfulServerExtension(myFhirContext)
+		.withServletPath("/fhir/*")
+		.withServer(t -> t.registerProvider(new HashMapResourceProvider<>(myFhirContext, Patient.class)))
+		.withServer(t -> t.registerProvider(new HashMapResourceProvider<>(myFhirContext, Observation.class)))
+		.withServer(t -> t.registerProvider(new OpenApiInterceptorTest.MyLastNProvider()))
+		.withServer(t -> t.registerInterceptor(new ResponseHighlighterInterceptor()));
+	private CloseableHttpClient myClient;
+	private AuthorizationInterceptor myAuthorizationInterceptor;
+	private List<IAuthRule> myRules;
+
+	@BeforeEach
+	public void before() {
+		myClient = HttpClientBuilder.create().build();
+		myAuthorizationInterceptor = new AuthorizationInterceptor() {
+			@Override
+			public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
+				return myRules;
+			}
+		};
+	}
+
+	@AfterEach
+	public void after() throws IOException {
+		myClient.close();
+		myServer.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
+	}
+
+	@Test
+	public void testFetchSwagger_AllowAll() throws IOException {
+		myServer.getRestfulServer().registerInterceptor(new OpenApiInterceptor());
+		myServer.getRestfulServer().registerInterceptor(myAuthorizationInterceptor);
+
+		myRules = new RuleBuilder()
+			.allowAll()
+			.build();
+
+		String resp;
+		HttpGet get;
+
+		get = new HttpGet("http://localhost:" + myServer.getPort() + "/fhir/api-docs");
+		try (CloseableHttpResponse response = myClient.execute(get)) {
+			resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+			ourLog.info("Response: {}", response.getStatusLine());
+			ourLog.info("Response: {}", resp);
+			assertEquals(200, response.getStatusLine().getStatusCode());
+		}
+
+		OpenAPI parsed = Yaml.mapper().readValue(resp, OpenAPI.class);
+		assertNotNull(parsed.getPaths().get("/Patient").getPost());
+	}
+}
diff --git a/hapi-fhir-server-openapi/src/test/resources/logback-test.xml b/hapi-fhir-server-openapi/src/test/resources/logback-test.xml
new file mode 100644
index 00000000000..2e29c0dfe82
--- /dev/null
+++ b/hapi-fhir-server-openapi/src/test/resources/logback-test.xml
@@ -0,0 +1,16 @@
+<configuration>
+
+	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+			<level>INFO</level>
+		</filter>
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n</pattern>
+		</encoder>
+	</appender>
+
+	<root level="info">
+		<appender-ref ref="STDOUT" />
+	</root>
+
+</configuration>
diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml
index 4fe87a2ee16..b0334c6fbbc 100644
--- a/hapi-fhir-server/pom.xml
+++ b/hapi-fhir-server/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java
index 5f7a405a59c..a2e541fb5fc 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java
@@ -7,6 +7,7 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
 import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
 import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
 import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
 import ca.uhn.fhir.util.StopWatch;
 import ca.uhn.fhir.util.UrlUtil;
 import org.apache.commons.lang3.Validate;
@@ -51,7 +52,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
 
 public abstract class RequestDetails {
 
-	private final StopWatch myRequestStopwatch = new StopWatch();
+	private final StopWatch myRequestStopwatch;
 	private IInterceptorBroadcaster myInterceptorBroadcaster;
 	private String myTenantId;
 	private String myCompartmentName;
@@ -81,6 +82,37 @@ public abstract class RequestDetails {
 	 */
 	public RequestDetails(IInterceptorBroadcaster theInterceptorBroadcaster) {
 		myInterceptorBroadcaster = theInterceptorBroadcaster;
+		myRequestStopwatch = new StopWatch();
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public RequestDetails(ServletRequestDetails theRequestDetails) {
+		myInterceptorBroadcaster = theRequestDetails.getInterceptorBroadcaster();
+		myRequestStopwatch = theRequestDetails.getRequestStopwatch();
+		myTenantId = theRequestDetails.getTenantId();
+		myCompartmentName = theRequestDetails.getCompartmentName();
+		myCompleteUrl = theRequestDetails.getCompleteUrl();
+		myFhirServerBase = theRequestDetails.getFhirServerBase();
+		myId = theRequestDetails.getId();
+		myOperation = theRequestDetails.getOperation();
+		myParameters = theRequestDetails.getParameters();
+		myRequestContents = theRequestDetails.getRequestContentsIfLoaded();
+		myRequestPath = theRequestDetails.getRequestPath();
+		myRequestType = theRequestDetails.getRequestType();
+		myResourceName = theRequestDetails.getResourceName();
+		myRespondGzip = theRequestDetails.isRespondGzip();
+		myResponse = theRequestDetails.getResponse();
+		myRestOperationType = theRequestDetails.getRestOperationType();
+		mySecondaryOperation = theRequestDetails.getSecondaryOperation();
+		mySubRequest = theRequestDetails.isSubRequest();
+		myUnqualifiedToQualifiedNames = theRequestDetails.getUnqualifiedToQualifiedNames();
+		myUserData = theRequestDetails.getUserData();
+		myResource = theRequestDetails.getResource();
+		myRequestId = theRequestDetails.getRequestId();
+		myTransactionGuid = theRequestDetails.getTransactionGuid();
+		myFixedConditionalUrl = theRequestDetails.getFixedConditionalUrl();
 	}
 
 	public String getFixedConditionalUrl() {
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/Bindings.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/Bindings.java
index a449d2bf88a..f17d1bf04c5 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/Bindings.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/Bindings.java
@@ -30,14 +30,14 @@ import java.util.List;
 public class Bindings {
 	private final IdentityHashMap<SearchMethodBinding, String> myNamedSearchMethodBindingToName;
 	private final HashMap<String, List<SearchMethodBinding>> mySearchNameToBindings;
-	private final HashMap<String, List<OperationMethodBinding>> myOperationNameToBindings;
-	private final IdentityHashMap<OperationMethodBinding, String> myOperationBindingToName;
+	private final HashMap<String, List<OperationMethodBinding>> myOperationIdToBindings;
+	private final IdentityHashMap<OperationMethodBinding, String> myOperationBindingToId;
 
-	public Bindings(IdentityHashMap<SearchMethodBinding, String> theNamedSearchMethodBindingToName, HashMap<String, List<SearchMethodBinding>> theSearchNameToBindings, HashMap<String, List<OperationMethodBinding>> theOperationNameToBindings, IdentityHashMap<OperationMethodBinding, String> theOperationBindingToName) {
+	public Bindings(IdentityHashMap<SearchMethodBinding, String> theNamedSearchMethodBindingToName, HashMap<String, List<SearchMethodBinding>> theSearchNameToBindings, HashMap<String, List<OperationMethodBinding>> theOperationIdToBindings, IdentityHashMap<OperationMethodBinding, String> theOperationBindingToName) {
 		myNamedSearchMethodBindingToName = theNamedSearchMethodBindingToName;
 		mySearchNameToBindings = theSearchNameToBindings;
-		myOperationNameToBindings = theOperationNameToBindings;
-		myOperationBindingToName = theOperationBindingToName;
+		myOperationIdToBindings = theOperationIdToBindings;
+		myOperationBindingToId = theOperationBindingToName;
 	}
 
 	public IdentityHashMap<SearchMethodBinding, String> getNamedSearchMethodBindingToName() {
@@ -48,11 +48,11 @@ public class Bindings {
 		return mySearchNameToBindings;
 	}
 
-	public HashMap<String, List<OperationMethodBinding>> getOperationNameToBindings() {
-		return myOperationNameToBindings;
+	public HashMap<String, List<OperationMethodBinding>> getOperationIdToBindings() {
+		return myOperationIdToBindings;
 	}
 
-	public IdentityHashMap<OperationMethodBinding, String> getOperationBindingToName() {
-		return myOperationBindingToName;
+	public IdentityHashMap<OperationMethodBinding, String> getOperationBindingToId() {
+		return myOperationBindingToId;
 	}
 }
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/IServerConformanceProvider.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/IServerConformanceProvider.java
index 1e7459c9068..854f625e268 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/IServerConformanceProvider.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/IServerConformanceProvider.java
@@ -22,8 +22,11 @@ package ca.uhn.fhir.rest.server;
 
 import javax.servlet.http.HttpServletRequest;
 
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Read;
 import ca.uhn.fhir.rest.api.server.RequestDetails;
 import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
 
 public interface IServerConformanceProvider<T extends IBaseResource> {
 
@@ -34,6 +37,11 @@ public interface IServerConformanceProvider<T extends IBaseResource> {
 	 */
 	T getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails);
 
+	@Read(typeName = "OperationDefinition")
+	default IBaseResource readOperationDefinition(@IdParam IIdType theId, RequestDetails theRequestDetails) {
+		return null;
+	}
+
 	/**
 	 * This setter is needed in implementation classes (along with
 	 * a no-arg constructor) to avoid reference cycles in the
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
index 51439570c7f..60385fd101a 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
@@ -67,6 +67,7 @@ import com.google.common.collect.Lists;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
+import org.hl7.fhir.instance.model.api.IBaseConformance;
 import org.hl7.fhir.instance.model.api.IBaseResource;
 import org.hl7.fhir.instance.model.api.IIdType;
 import org.slf4j.Logger;
@@ -148,6 +149,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
 	private FhirContext myFhirContext;
 	private boolean myIgnoreServerParsedRequestParameters = true;
 	private String myImplementationDescription;
+	private String myCopyright;
 	private IPagingProvider myPagingProvider;
 	private Integer myDefaultPageSize;
 	private Integer myMaximumPageSize;
@@ -157,7 +159,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
 	private IServerAddressStrategy myServerAddressStrategy = new IncomingRequestAddressStrategy();
 	private ResourceBinding myServerBinding = new ResourceBinding();
 	private ResourceBinding myGlobalBinding = new ResourceBinding();
-	private BaseMethodBinding<?> myServerConformanceMethod;
+	private ConformanceMethodBinding myServerConformanceMethod;
 	private Object myServerConformanceProvider;
 	private String myServerName = "HAPI FHIR Server";
 	/**
@@ -234,6 +236,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
 		RestfulServerConfiguration result = new RestfulServerConfiguration();
 		result.setResourceBindings(getResourceBindings());
 		result.setServerBindings(getServerBindings());
+		result.setGlobalBindings(getGlobalBindings());
 		result.setImplementationDescription(getImplementationDescription());
 		result.setServerVersion(getServerVersion());
 		result.setServerName(getServerName());
@@ -252,6 +255,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
 		return result;
 	}
 
+	private List<BaseMethodBinding<?>> getGlobalBindings() {
+		return myGlobalBinding.getMethodBindings();
+	}
+
 	protected List<String> createPoweredByAttributes() {
 		return Lists.newArrayList("FHIR Server", "FHIR " + myFhirContext.getVersion().getVersion().getFhirVersionString() + "/" + myFhirContext.getVersion().getVersion().name());
 	}
@@ -458,7 +465,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
 			count++;
 
 			if (foundMethodBinding instanceof ConformanceMethodBinding) {
-				myServerConformanceMethod = foundMethodBinding;
+				myServerConformanceMethod = (ConformanceMethodBinding) foundMethodBinding;
+				if (myServerConformanceProvider == null) {
+					myServerConformanceProvider = theProvider;
+				}
 				continue;
 			}
 
@@ -635,6 +645,20 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
 		myImplementationDescription = theImplementationDescription;
 	}
 
+	/**
+	 * Returns the server copyright (will be added to the CapabilityStatement). Note that FHIR allows Markdown in this string.
+	 */
+	public String getCopyright() {
+		return myCopyright;
+	}
+
+	/**
+	 * Sets the server copyright (will be added to the CapabilityStatement). Note that FHIR allows Markdown in this string.
+	 */
+	public void setCopyright(String theCopyright) {
+		myCopyright = theCopyright;
+	}
+
 	/**
 	 * Returns a list of all registered server interceptors
 	 *
@@ -806,8 +830,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
 	/**
 	 * Provides the resource providers for this server
 	 */
-	public Collection<IResourceProvider> getResourceProviders() {
-		return myResourceProviders;
+	public List<IResourceProvider> getResourceProviders() {
+		return Collections.unmodifiableList(myResourceProviders);
 	}
 
 	/**
@@ -853,6 +877,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
 	public String getServerBaseForRequest(ServletRequestDetails theRequest) {
 		String fhirServerBase;
 		fhirServerBase = myServerAddressStrategy.determineServerBase(getServletContext(), theRequest.getServletRequest());
+		assert isNotBlank(fhirServerBase) : "Server Address Strategy did not return a value";
 
 		if (fhirServerBase.endsWith("/")) {
 			fhirServerBase = fhirServerBase.substring(0, fhirServerBase.length() - 1);
@@ -1363,9 +1388,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
 				 */
 				findResourceMethods(new PageProvider());
 
-			} catch (Exception ex) {
-				ourLog.error("An error occurred while loading request handlers!", ex);
-				throw new ServletException("Failed to initialize FHIR Restful server", ex);
+			} catch (Exception e) {
+				ourLog.error("An error occurred while loading request handlers!", e);
+				throw new ServletException("Failed to initialize FHIR Restful server: " + e.getMessage(), e);
 			}
 
 			myStarted = true;
@@ -1984,6 +2009,17 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
 		myDefaultPreferReturn = theDefaultPreferReturn;
 	}
 
+	/**
+	 * Create a CapabilityStatement based on the given request
+	 */
+	public IBaseConformance getCapabilityStatement(ServletRequestDetails theRequestDetails) {
+		// Create a cloned request details so we can make it indicate that this is a capabilities request
+		ServletRequestDetails requestDetails = new ServletRequestDetails(theRequestDetails);
+		requestDetails.setRestOperationType(RestOperationTypeEnum.METADATA);
+
+		return myServerConformanceMethod.provideCapabilityStatement(this, requestDetails);
+	}
+
 	/**
 	 * Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20)
 	 */
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerConfiguration.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerConfiguration.java
index 568e3624149..7c7157a5940 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerConfiguration.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerConfiguration.java
@@ -30,6 +30,9 @@ import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
 import ca.uhn.fhir.rest.server.method.SearchParameter;
 import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
 import ca.uhn.fhir.util.VersionUtil;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimap;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
 import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -46,27 +49,32 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.stream.Collectors;
 
 import static org.apache.commons.lang3.StringUtils.isBlank;
 
 public class RestfulServerConfiguration implements ISearchParamRegistry {
 
+	public static final String GLOBAL = "GLOBAL";
 	private static final Logger ourLog = LoggerFactory.getLogger(RestfulServerConfiguration.class);
 	private Collection<ResourceBinding> resourceBindings;
 	private List<BaseMethodBinding<?>> serverBindings;
+	private List<BaseMethodBinding<?>> myGlobalBindings;
 	private Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype;
-	private String implementationDescription;
-	private String serverVersion = VersionUtil.getVersion();
-	private String serverName = "HAPI FHIR";
-	private FhirContext fhirContext;
-	private IServerAddressStrategy serverAddressStrategy;
+	private String myImplementationDescription;
+	private String myServerName = "HAPI FHIR";
+	private String myServerVersion = VersionUtil.getVersion();
+	private FhirContext myFhirContext;
+	private IServerAddressStrategy myServerAddressStrategy;
 	private IPrimitiveType<Date> myConformanceDate;
 
 	/**
@@ -127,10 +135,10 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 	 * @return the implementationDescription
 	 */
 	public String getImplementationDescription() {
-		if (isBlank(implementationDescription)) {
+		if (isBlank(myImplementationDescription)) {
 			return "HAPI FHIR";
 		}
-		return implementationDescription;
+		return myImplementationDescription;
 	}
 
 	/**
@@ -139,7 +147,7 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 	 * @param implementationDescription the implementationDescription to set
 	 */
 	public RestfulServerConfiguration setImplementationDescription(String implementationDescription) {
-		this.implementationDescription = implementationDescription;
+		this.myImplementationDescription = implementationDescription;
 		return this;
 	}
 
@@ -149,7 +157,7 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 	 * @return the serverVersion
 	 */
 	public String getServerVersion() {
-		return serverVersion;
+		return myServerVersion;
 	}
 
 	/**
@@ -158,7 +166,7 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 	 * @param serverVersion the serverVersion to set
 	 */
 	public RestfulServerConfiguration setServerVersion(String serverVersion) {
-		this.serverVersion = serverVersion;
+		this.myServerVersion = serverVersion;
 		return this;
 	}
 
@@ -168,7 +176,7 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 	 * @return the serverName
 	 */
 	public String getServerName() {
-		return serverName;
+		return myServerName;
 	}
 
 	/**
@@ -177,7 +185,7 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 	 * @param serverName the serverName to set
 	 */
 	public RestfulServerConfiguration setServerName(String serverName) {
-		this.serverName = serverName;
+		this.myServerName = serverName;
 		return this;
 	}
 
@@ -186,7 +194,7 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 	 * creating their own.
 	 */
 	public FhirContext getFhirContext() {
-		return this.fhirContext;
+		return this.myFhirContext;
 	}
 
 	/**
@@ -195,7 +203,7 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 	 * @param fhirContext the fhirContext to set
 	 */
 	public RestfulServerConfiguration setFhirContext(FhirContext fhirContext) {
-		this.fhirContext = fhirContext;
+		this.myFhirContext = fhirContext;
 		return this;
 	}
 
@@ -205,7 +213,7 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 	 * @return the serverAddressStrategy
 	 */
 	public IServerAddressStrategy getServerAddressStrategy() {
-		return serverAddressStrategy;
+		return myServerAddressStrategy;
 	}
 
 	/**
@@ -214,7 +222,7 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 	 * @param serverAddressStrategy the serverAddressStrategy to set
 	 */
 	public void setServerAddressStrategy(IServerAddressStrategy serverAddressStrategy) {
-		this.serverAddressStrategy = serverAddressStrategy;
+		this.myServerAddressStrategy = serverAddressStrategy;
 	}
 
 	/**
@@ -236,48 +244,114 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 	}
 
 	public Bindings provideBindings() {
-		IdentityHashMap<SearchMethodBinding, String> myNamedSearchMethodBindingToName = new IdentityHashMap<>();
-		HashMap<String, List<SearchMethodBinding>> mySearchNameToBindings = new HashMap<>();
-		IdentityHashMap<OperationMethodBinding, String> myOperationBindingToName = new IdentityHashMap<>();
-		HashMap<String, List<OperationMethodBinding>> myOperationNameToBindings = new HashMap<>();
+		IdentityHashMap<SearchMethodBinding, String> namedSearchMethodBindingToName = new IdentityHashMap<>();
+		HashMap<String, List<SearchMethodBinding>> searchNameToBindings = new HashMap<>();
+		IdentityHashMap<OperationMethodBinding, String> operationBindingToId = new IdentityHashMap<>();
+		HashMap<String, List<OperationMethodBinding>> operationIdToBindings = new HashMap<>();
 
 		Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings();
-		for (Map.Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
-			List<BaseMethodBinding<?>> nextMethodBindings = nextEntry.getValue();
-			for (BaseMethodBinding<?> nextMethodBinding : nextMethodBindings) {
-				if (nextMethodBinding instanceof OperationMethodBinding) {
-					OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
-					if (myOperationBindingToName.containsKey(methodBinding)) {
-						continue;
-					}
+		List<BaseMethodBinding<?>> methodBindings = resourceToMethods
+			.values()
+			.stream().flatMap(t -> t.stream())
+			.collect(Collectors.toList());
+		if (myGlobalBindings != null) {
+			methodBindings.addAll(myGlobalBindings);
+		}
 
-					String name = createOperationName(methodBinding);
-					ourLog.debug("Detected operation: {}", name);
-
-					myOperationBindingToName.put(methodBinding, name);
-					if (myOperationNameToBindings.containsKey(name) == false) {
-						myOperationNameToBindings.put(name, new ArrayList<>());
-					}
-					myOperationNameToBindings.get(name).add(methodBinding);
-				} else if (nextMethodBinding instanceof SearchMethodBinding) {
-					SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding;
-					if (myNamedSearchMethodBindingToName.containsKey(methodBinding)) {
-						continue;
-					}
-
-					String name = createNamedQueryName(methodBinding);
-					ourLog.debug("Detected named query: {}", name);
-
-					myNamedSearchMethodBindingToName.put(methodBinding, name);
-					if (!mySearchNameToBindings.containsKey(name)) {
-						mySearchNameToBindings.put(name, new ArrayList<>());
-					}
-					mySearchNameToBindings.get(name).add(methodBinding);
+		ListMultimap<String, OperationMethodBinding> nameToOperationMethodBindings = ArrayListMultimap.create();
+		for (BaseMethodBinding<?> nextMethodBinding : methodBindings) {
+			if (nextMethodBinding instanceof OperationMethodBinding) {
+				OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
+				nameToOperationMethodBindings.put(methodBinding.getName(), methodBinding);
+			} else if (nextMethodBinding instanceof SearchMethodBinding) {
+				SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding;
+				if (namedSearchMethodBindingToName.containsKey(methodBinding)) {
+					continue;
 				}
+
+				String name = createNamedQueryName(methodBinding);
+				ourLog.debug("Detected named query: {}", name);
+
+				namedSearchMethodBindingToName.put(methodBinding, name);
+				if (!searchNameToBindings.containsKey(name)) {
+					searchNameToBindings.put(name, new ArrayList<>());
+				}
+				searchNameToBindings.get(name).add(methodBinding);
 			}
 		}
 
-		return new Bindings(myNamedSearchMethodBindingToName, mySearchNameToBindings, myOperationNameToBindings, myOperationBindingToName);
+		for (String nextName : nameToOperationMethodBindings.keySet()) {
+			List<OperationMethodBinding> nextMethodBindings = nameToOperationMethodBindings.get(nextName);
+
+			boolean global = false;
+			boolean system = false;
+			boolean instance = false;
+			boolean type = false;
+			Set<String> resourceTypes = null;
+
+			for (OperationMethodBinding nextMethodBinding : nextMethodBindings) {
+				global |= nextMethodBinding.isGlobalMethod();
+				system |= nextMethodBinding.isCanOperateAtServerLevel();
+				type |= nextMethodBinding.isCanOperateAtTypeLevel();
+				instance |= nextMethodBinding.isCanOperateAtInstanceLevel();
+				if (nextMethodBinding.getResourceName() != null) {
+					resourceTypes = resourceTypes != null ? resourceTypes : new TreeSet<>();
+					resourceTypes.add(nextMethodBinding.getResourceName());
+				}
+			}
+
+			StringBuilder operationIdBuilder = new StringBuilder();
+			if (global) {
+				operationIdBuilder.append("Global");
+			} else if (resourceTypes != null && resourceTypes.size() == 1) {
+				operationIdBuilder.append(resourceTypes.iterator().next());
+			} else if (resourceTypes != null && resourceTypes.size() == 2) {
+				Iterator<String> iterator = resourceTypes.iterator();
+				operationIdBuilder.append(iterator.next());
+				operationIdBuilder.append(iterator.next());
+			} else if (resourceTypes != null) {
+				operationIdBuilder.append("Multi");
+			}
+
+			operationIdBuilder.append('-');
+			if (instance) {
+				operationIdBuilder.append('i');
+			}
+			if (type) {
+				operationIdBuilder.append('t');
+			}
+			if (system) {
+				operationIdBuilder.append('s');
+			}
+			operationIdBuilder.append('-');
+
+			// Exclude the leading $
+			operationIdBuilder.append(nextName, 1, nextName.length());
+
+			String operationId = operationIdBuilder.toString();
+			operationIdToBindings.put(operationId, nextMethodBindings);
+			nextMethodBindings.forEach(t->operationBindingToId.put(t, operationId));
+		}
+
+		for (BaseMethodBinding<?> nextMethodBinding : methodBindings) {
+			if (nextMethodBinding instanceof OperationMethodBinding) {
+				OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
+				if (operationBindingToId.containsKey(methodBinding)) {
+					continue;
+				}
+
+				String name = createOperationName(methodBinding);
+				ourLog.debug("Detected operation: {}", name);
+
+				operationBindingToId.put(methodBinding, name);
+				if (operationIdToBindings.containsKey(name) == false) {
+					operationIdToBindings.put(name, new ArrayList<>());
+				}
+				operationIdToBindings.get(name).add(methodBinding);
+			}
+		}
+
+		return new Bindings(namedSearchMethodBindingToName, searchNameToBindings, operationIdToBindings, operationBindingToId);
 	}
 
 	public Map<String, List<BaseMethodBinding<?>>> collectMethodBindings() {
@@ -301,6 +375,14 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 		return resourceToMethods;
 	}
 
+	public List<BaseMethodBinding<?>> getGlobalBindings() {
+		return myGlobalBindings;
+	}
+
+	public void setGlobalBindings(List<BaseMethodBinding<?>> theGlobalBindings) {
+		myGlobalBindings = theGlobalBindings;
+	}
+
 	/*
 	 * Populates {@link #resourceNameToSharedSupertype} by scanning the given resource providers. Only resource provider getResourceType values
 	 * are taken into account. {@link ProvidesResources} and method return types are deliberately ignored.
@@ -329,28 +411,6 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 				entry -> entry.getValue().getLowestCommonSuperclass().get()));
 	}
 
-	private String createOperationName(OperationMethodBinding theMethodBinding) {
-		StringBuilder retVal = new StringBuilder();
-		if (theMethodBinding.getResourceName() != null) {
-			retVal.append(theMethodBinding.getResourceName());
-		}
-
-		retVal.append('-');
-		if (theMethodBinding.isCanOperateAtInstanceLevel()) {
-			retVal.append('i');
-		}
-		if (theMethodBinding.isCanOperateAtServerLevel()) {
-			retVal.append('s');
-		}
-		retVal.append('-');
-
-		// Exclude the leading $
-		retVal.append(theMethodBinding.getName(), 1, theMethodBinding.getName().length());
-
-		return retVal.toString();
-	}
-
-
 	private String createNamedQueryName(SearchMethodBinding searchMethodBinding) {
 		StringBuilder retVal = new StringBuilder();
 		if (searchMethodBinding.getResourceName() != null) {
@@ -443,7 +503,6 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 
 	}
 
-
 	private static class SearchParameterComparator implements Comparator<SearchParameter> {
 		private static final SearchParameterComparator INSTANCE = new SearchParameterComparator();
 
@@ -458,4 +517,27 @@ public class RestfulServerConfiguration implements ISearchParamRegistry {
 			return 1;
 		}
 	}
+
+	private static String createOperationName(OperationMethodBinding theMethodBinding) {
+		StringBuilder retVal = new StringBuilder();
+		if (theMethodBinding.getResourceName() != null) {
+			retVal.append(theMethodBinding.getResourceName());
+		} else if (theMethodBinding.isGlobalMethod()) {
+			retVal.append("Global");
+		}
+
+		retVal.append('-');
+		if (theMethodBinding.isCanOperateAtInstanceLevel()) {
+			retVal.append('i');
+		}
+		if (theMethodBinding.isCanOperateAtServerLevel()) {
+			retVal.append('s');
+		}
+		retVal.append('-');
+
+		// Exclude the leading $
+		retVal.append(theMethodBinding.getName(), 1, theMethodBinding.getName().length());
+
+		return retVal.toString();
+	}
 }
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java
index 8b83bc55a56..229a7e49802 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java
@@ -41,6 +41,7 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetai
 import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
 import ca.uhn.fhir.util.ReflectionUtil;
 import org.hl7.fhir.instance.model.api.IAnyResource;
+import org.hl7.fhir.instance.model.api.IBaseBundle;
 import org.hl7.fhir.instance.model.api.IBaseResource;
 
 import javax.annotation.Nonnull;
@@ -386,6 +387,10 @@ public abstract class BaseMethodBinding<T> {
 					+ " returns a collection with generic type " + toLogString(returnTypeFromMethod)
 					+ " - Must return a resource type or a collection (List, Set) with a resource type parameter (e.g. List<Patient> or List<IBaseResource> )");
 			}
+		} else if (IBaseBundle.class.isAssignableFrom(returnTypeFromMethod) && returnTypeFromRp == null) {
+			// If a plain provider method returns a Bundle, we'll assume it to be a system
+			// level operation and not a type/instance level operation on the Bundle type.
+			returnTypeFromMethod = null;
 		} else {
 			if (!isResourceInterface(returnTypeFromMethod) && !verifyIsValidResourceReturnType(returnTypeFromMethod)) {
 				throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
@@ -395,30 +400,41 @@ public abstract class BaseMethodBinding<T> {
 		}
 
 		Class<? extends IBaseResource> returnTypeFromAnnotation = IBaseResource.class;
+		String returnTypeNameFromAnnotation = null;
 		if (read != null) {
-			if (isNotBlank(read.typeName())) {
-				returnTypeFromAnnotation = theContext.getResourceDefinition(read.typeName()).getImplementingClass();
-			} else {
-				returnTypeFromAnnotation = read.type();
-			}
+			returnTypeFromAnnotation = read.type();
+			returnTypeNameFromAnnotation = read.typeName();
 		} else if (search != null) {
 			returnTypeFromAnnotation = search.type();
+			returnTypeNameFromAnnotation = search.typeName();
 		} else if (history != null) {
 			returnTypeFromAnnotation = history.type();
+			returnTypeNameFromAnnotation = history.typeName();
 		} else if (delete != null) {
 			returnTypeFromAnnotation = delete.type();
+			returnTypeNameFromAnnotation = delete.typeName();
 		} else if (patch != null) {
 			returnTypeFromAnnotation = patch.type();
+			returnTypeNameFromAnnotation = patch.typeName();
 		} else if (create != null) {
 			returnTypeFromAnnotation = create.type();
+			returnTypeNameFromAnnotation = create.typeName();
 		} else if (update != null) {
 			returnTypeFromAnnotation = update.type();
+			returnTypeNameFromAnnotation = update.typeName();
 		} else if (validate != null) {
 			returnTypeFromAnnotation = validate.type();
+			returnTypeNameFromAnnotation = validate.typeName();
 		} else if (addTags != null) {
 			returnTypeFromAnnotation = addTags.type();
+			returnTypeNameFromAnnotation = addTags.typeName();
 		} else if (deleteTags != null) {
 			returnTypeFromAnnotation = deleteTags.type();
+			returnTypeNameFromAnnotation = deleteTags.typeName();
+		}
+
+		if (isNotBlank(returnTypeNameFromAnnotation)) {
+			returnTypeFromAnnotation = theContext.getResourceDefinition(returnTypeNameFromAnnotation).getImplementingClass();
 		}
 
 		if (returnTypeFromRp != null) {
@@ -477,7 +493,7 @@ public abstract class BaseMethodBinding<T> {
 	}
 
 	private static boolean isResourceInterface(Class<?> theReturnTypeFromMethod) {
-		return theReturnTypeFromMethod.equals(IBaseResource.class) || theReturnTypeFromMethod.equals(IResource.class) || theReturnTypeFromMethod.equals(IAnyResource.class);
+		return theReturnTypeFromMethod != null && (theReturnTypeFromMethod.equals(IBaseResource.class) || theReturnTypeFromMethod.equals(IResource.class) || theReturnTypeFromMethod.equals(IAnyResource.class));
 	}
 
 	private static String toLogString(Class<?> theType) {
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java
index 5c37205e17b..302eabac9ea 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java
@@ -27,6 +27,8 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
 import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
 import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
 import ca.uhn.fhir.util.ReflectionUtil;
+import ca.uhn.fhir.util.ValidateUtil;
+import org.apache.commons.lang3.Validate;
 import org.hl7.fhir.instance.model.api.IBaseBundle;
 import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
 import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -79,7 +81,13 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
 		super(theMethod, theContext, theProvider);
 
 		Class<?> methodReturnType = theMethod.getReturnType();
-		if (Collection.class.isAssignableFrom(methodReturnType)) {
+
+		Set<Class<?>> expectedReturnTypes = provideExpectedReturnTypes();
+		if (expectedReturnTypes != null) {
+
+			Validate.isTrue(expectedReturnTypes.contains(methodReturnType), "Unexpected method return type on %s - Allowed: %s", theMethod, expectedReturnTypes);
+
+		} else if (Collection.class.isAssignableFrom(methodReturnType)) {
 
 			myMethodReturnType = MethodReturnTypeEnum.LIST_OF_RESOURCES;
 			Class<?> collectionType = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod);
@@ -123,6 +131,13 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
 
 	}
 
+	/**
+	 * Subclasses may override
+	 */
+	protected Set<Class<?>> provideExpectedReturnTypes() {
+		return null;
+	}
+
 	IBaseResource createBundleFromBundleProvider(IRestfulServer<?> theServer, RequestDetails theRequest, Integer theLimit, String theLinkSelf, Set<Include> theIncludes,
 																IBundleProvider theResult, int theOffset, BundleTypeEnum theBundleType, EncodingEnum theLinkEncoding, String theSearchId) {
 		IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory();
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ConformanceMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ConformanceMethodBinding.java
index 02db0a82632..1930d176f5b 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ConformanceMethodBinding.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ConformanceMethodBinding.java
@@ -33,6 +33,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
 import ca.uhn.fhir.rest.api.server.IBundleProvider;
 import ca.uhn.fhir.rest.api.server.IRestfulServer;
 import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.server.RestfulServer;
 import ca.uhn.fhir.rest.server.SimpleBundleProvider;
 import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
 import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
@@ -194,6 +195,7 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
 			myCachedResponse.set(conf);
 			myCachedResponseExpires.set(System.currentTimeMillis() + getCacheMillis());
 		}
+		
 		return conf;
 	}
 
@@ -230,4 +232,13 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
 		return null;
 	}
 
+	/**
+	 * Create and return the server's CapabilityStatement
+	 */
+	public IBaseConformance provideCapabilityStatement(RestfulServer theServer, RequestDetails theRequest) {
+		Object[] params = createMethodParams(theRequest);
+		IBundleProvider resultObj = invokeServer(theServer, theRequest, params);
+		return (IBaseConformance) resultObj.getResources(0,1).get(0);
+	}
+
 }
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/GraphQLMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/GraphQLMethodBinding.java
index 96401f0dd04..35779595f40 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/GraphQLMethodBinding.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/GraphQLMethodBinding.java
@@ -42,8 +42,10 @@ import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.Writer;
 import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Set;
 
-public class GraphQLMethodBinding extends BaseMethodBinding<String> {
+public class GraphQLMethodBinding extends OperationMethodBinding {
 
 	private final Integer myIdParamIndex;
 	private final Integer myQueryUrlParamIndex;
@@ -51,7 +53,7 @@ public class GraphQLMethodBinding extends BaseMethodBinding<String> {
 	private final RequestTypeEnum myMethodRequestType;
 
 	public GraphQLMethodBinding(Method theMethod, RequestTypeEnum theMethodRequestType, FhirContext theContext, Object theProvider) {
-		super(theMethod, theContext, theProvider);
+		super(null, null, theMethod, theContext, theProvider, true, Constants.OPERATION_NAME_GRAPHQL, null, null, null, null, true);
 
 		myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, theContext);
 		myQueryUrlParamIndex = ParameterUtil.findParamAnnotationIndex(theMethod, GraphQLQueryUrl.class);
@@ -71,10 +73,30 @@ public class GraphQLMethodBinding extends BaseMethodBinding<String> {
 	}
 
 	@Override
-	public boolean isGlobalMethod() {
+	public RestOperationTypeEnum getRestOperationType(RequestDetails theRequestDetails) {
+		return getRestOperationType();
+	}
+
+	@Override
+	protected Set<Class<?>> provideExpectedReturnTypes() {
+		return Collections.singleton(String.class);
+	}
+
+	@Override
+	public boolean isCanOperateAtServerLevel() {
 		return true;
 	}
 
+	@Override
+	public boolean isCanOperateAtTypeLevel() {
+		return false;
+	}
+
+	@Override
+	public boolean isCanOperateAtInstanceLevel() {
+		return myIdParamIndex != null;
+	}
+
 	@Override
 	public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) {
 		if (Constants.OPERATION_NAME_GRAPHQL.equals(theRequest.getOperation()) && myMethodRequestType.equals(theRequest.getRequestType())) {
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodUtil.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodUtil.java
index e01d1daa859..31a608c2846 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodUtil.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodUtil.java
@@ -52,15 +52,19 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
 
 public class MethodUtil {
 
+	/**
+	 * Non instantiable
+	 */
+	private MethodUtil() {
+		// nothing
+	}
+
 	public static void extractDescription(SearchParameter theParameter, Annotation[] theAnnotations) {
 		for (Annotation annotation : theAnnotations) {
 			if (annotation instanceof Description) {
 				Description desc = (Description) annotation;
-				if (isNotBlank(desc.formalDefinition())) {
-					theParameter.setDescription(desc.formalDefinition());
-				} else {
-					theParameter.setDescription(desc.shortDefinition());
-				}
+				String description = ParametersUtil.extractDescription(desc);
+				theParameter.setDescription(description);
 			}
 		}
 	}
@@ -72,7 +76,7 @@ public class MethodUtil {
 
 		Class<?>[] parameterTypes = theMethod.getParameterTypes();
 		int paramIndex = 0;
-		for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
+		for (Annotation[] nextParameterAnnotations : theMethod.getParameterAnnotations()) {
 
 			IParameter param = null;
 			Class<?> declaredParameterType = parameterTypes[paramIndex];
@@ -136,8 +140,8 @@ public class MethodUtil {
 			} else if (parameterType.equals(SearchTotalModeEnum.class)) {
 				param = new SearchTotalModeParameter();
 			} else {
-				for (int i = 0; i < annotations.length && param == null; i++) {
-					Annotation nextAnnotation = annotations[i];
+				for (int i = 0; i < nextParameterAnnotations.length && param == null; i++) {
+					Annotation nextAnnotation = nextParameterAnnotations[i];
 
 					if (nextAnnotation instanceof RequiredParam) {
 						SearchParameter parameter = new SearchParameter();
@@ -147,7 +151,7 @@ public class MethodUtil {
 						parameter.setCompositeTypes(((RequiredParam) nextAnnotation).compositeTypes());
 						parameter.setChainLists(((RequiredParam) nextAnnotation).chainWhitelist(), ((RequiredParam) nextAnnotation).chainBlacklist());
 						parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType);
-						MethodUtil.extractDescription(parameter, annotations);
+						MethodUtil.extractDescription(parameter, nextParameterAnnotations);
 						param = parameter;
 					} else if (nextAnnotation instanceof OptionalParam) {
 						SearchParameter parameter = new SearchParameter();
@@ -157,7 +161,7 @@ public class MethodUtil {
 						parameter.setCompositeTypes(((OptionalParam) nextAnnotation).compositeTypes());
 						parameter.setChainLists(((OptionalParam) nextAnnotation).chainWhitelist(), ((OptionalParam) nextAnnotation).chainBlacklist());
 						parameter.setType(theContext, parameterType, innerCollectionType, outerCollectionType);
-						MethodUtil.extractDescription(parameter, annotations);
+						MethodUtil.extractDescription(parameter, nextParameterAnnotations);
 						param = parameter;
 					} else if (nextAnnotation instanceof RawParam) {
 						param = new RawParamsParameter(parameters);
@@ -235,7 +239,9 @@ public class MethodUtil {
 						}
 
 						OperationParam operationParam = (OperationParam) nextAnnotation;
-						param = new OperationParameter(theContext, op.name(), operationParam);
+						String description = ParametersUtil.extractDescription(nextParameterAnnotations);
+						List<String> examples = ParametersUtil.extractExamples(nextParameterAnnotations);;
+						param = new OperationParameter(theContext, op.name(), operationParam.name(), operationParam.min(), operationParam.max(), description, examples);
 						if (isNotBlank(operationParam.typeName())) {
 							BaseRuntimeElementDefinition<?> elementDefinition = theContext.getElementDefinition(operationParam.typeName());
 							if (elementDefinition == null) {
@@ -254,7 +260,9 @@ public class MethodUtil {
 							throw new ConfigurationException(
 								"Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Mode.class.getSimpleName() + " must be of type " + ValidationModeEnum.class.getName());
 						}
-						param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_MODE, 0, 1).setConverter(new IOperationParamConverter() {
+						String description = ParametersUtil.extractDescription(nextParameterAnnotations);
+						List<String> examples = ParametersUtil.extractExamples(nextParameterAnnotations);
+						param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_MODE, 0, 1, description, examples).setConverter(new IOperationParamConverter() {
 							@Override
 							public Object incomingServer(Object theObject) {
 								if (isNotBlank(theObject.toString())) {
@@ -277,7 +285,9 @@ public class MethodUtil {
 							throw new ConfigurationException(
 								"Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Profile.class.getSimpleName() + " must be of type " + String.class.getName());
 						}
-						param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_PROFILE, 0, 1).setConverter(new IOperationParamConverter() {
+						String description = ParametersUtil.extractDescription(nextParameterAnnotations);
+						List<String> examples = ParametersUtil.extractExamples(nextParameterAnnotations);
+						param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_PROFILE, 0, 1, description, examples).setConverter(new IOperationParamConverter() {
 							@Override
 							public Object incomingServer(Object theObject) {
 								return theObject.toString();
@@ -299,7 +309,7 @@ public class MethodUtil {
 			if (param == null) {
 				throw new ConfigurationException(
 					"Parameter #" + ((paramIndex + 1)) + "/" + (parameterTypes.length) + " of method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName()
-						+ "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter");
+						+ "' has no recognized FHIR interface parameter nextParameterAnnotations. Don't know how to handle this parameter");
 			}
 
 			param.initializeTypes(theMethod, outerCollectionType, innerCollectionType, parameterType);
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java
index 5af53c33af2..b3dc81fe3f5 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java
@@ -22,7 +22,6 @@ package ca.uhn.fhir.rest.server.method;
 
 import ca.uhn.fhir.context.ConfigurationException;
 import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.model.api.annotation.Description;
 import ca.uhn.fhir.model.valueset.BundleTypeEnum;
 import ca.uhn.fhir.parser.DataFormatException;
 import ca.uhn.fhir.rest.annotation.IdParam;
@@ -39,6 +38,7 @@ import ca.uhn.fhir.rest.param.ParameterUtil;
 import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
 import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
 import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
+import ca.uhn.fhir.util.ParametersUtil;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 import org.hl7.fhir.instance.model.api.IBase;
@@ -64,6 +64,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
 	private final String myName;
 	private final RestOperationTypeEnum myOtherOperationType;
 	private final ReturnTypeEnum myReturnType;
+	private final String myShortDescription;
 	private boolean myGlobal;
 	private BundleTypeEnum myBundleType;
 	private boolean myCanOperateAtInstanceLevel;
@@ -74,24 +75,29 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
 	private boolean myManualRequestMode;
 	private boolean myManualResponseMode;
 
+	/**
+	 * Constructor - This is the constructor that is called when binding a
+	 * standard @Operation method.
+	 */
+	public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
+											Operation theAnnotation) {
+		this(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, theAnnotation.idempotent(), theAnnotation.name(), theAnnotation.type(), theAnnotation.typeName(), theAnnotation.returnParameters(),
+			theAnnotation.bundleType(), theAnnotation.global());
+
+		myManualRequestMode = theAnnotation.manualRequest();
+		myManualResponseMode = theAnnotation.manualResponse();
+	}
+
 	protected OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
 												boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType, String theOperationTypeName,
-												OperationParam[] theReturnParams, BundleTypeEnum theBundleType) {
+												OperationParam[] theReturnParams, BundleTypeEnum theBundleType, boolean theGlobal) {
 		super(theReturnResourceType, theMethod, theContext, theProvider);
 
 		myBundleType = theBundleType;
 		myIdempotent = theIdempotent;
-
-		Description description = theMethod.getAnnotation(Description.class);
-		if (description != null) {
-			myDescription = description.formalDefinition();
-			if (isBlank(myDescription)) {
-				myDescription = description.shortDefinition();
-			}
-		}
-		if (isBlank(myDescription)) {
-			myDescription = null;
-		}
+		myDescription = ParametersUtil.extractDescription(theMethod);
+		myShortDescription = ParametersUtil.extractShortDefinition(theMethod);
+		myGlobal = theGlobal;
 
 		for (Annotation[] nextParamAnnotations : theMethod.getParameterAnnotations()) {
 			for (Annotation nextParam : nextParamAnnotations) {
@@ -113,7 +119,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
 		try {
 			if (theReturnTypeFromRp != null) {
 				setResourceName(theContext.getResourceType(theReturnTypeFromRp));
-			} else if (Modifier.isAbstract(theOperationType.getModifiers()) == false) {
+			} else if (theOperationType != null && Modifier.isAbstract(theOperationType.getModifiers()) == false) {
 				setResourceName(theContext.getResourceType(theOperationType));
 			} else if (isNotBlank(theOperationTypeName)) {
 				setResourceName(theContext.getResourceType(theOperationTypeName));
@@ -133,9 +139,10 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
 		myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext());
 		if (getResourceName() == null) {
 			myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_SERVER;
-			myCanOperateAtServerLevel = true;
 			if (myIdParamIndex != null) {
 				myCanOperateAtInstanceLevel = true;
+			} else {
+				myCanOperateAtServerLevel = true;
 			}
 		} else if (myIdParamIndex == null) {
 			myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_TYPE;
@@ -169,20 +176,16 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
 				myReturnParams.add(type);
 			}
 		}
+
+		// Parameter Validation
+		if (myCanOperateAtInstanceLevel && !isGlobalMethod() && getResourceName() == null) {
+			throw new ConfigurationException("@" + Operation.class.getSimpleName() + " method is an instance level method (it has an @" + IdParam.class.getSimpleName() + " parameter) but is not marked as global() and is not declared in a resource provider: " + theMethod.getName());
+		}
+
 	}
 
-	/**
-	 * Constructor - This is the constructor that is called when binding a
-	 * standard @Operation method.
-	 */
-	public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
-											Operation theAnnotation) {
-		this(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, theAnnotation.idempotent(), theAnnotation.name(), theAnnotation.type(), theAnnotation.typeName(), theAnnotation.returnParameters(),
-			theAnnotation.bundleType());
-
-		myManualRequestMode = theAnnotation.manualRequest();
-		myManualResponseMode = theAnnotation.manualResponse();
-		myGlobal = theAnnotation.global();
+	public String getShortDescription() {
+		return myShortDescription;
 	}
 
 	@Override
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationParameter.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationParameter.java
index fc79dfdb0c5..13a9d115b2d 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationParameter.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationParameter.java
@@ -20,6 +20,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
 import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
 import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
 import ca.uhn.fhir.util.FhirTerser;
+import ca.uhn.fhir.util.ParametersUtil;
 import ca.uhn.fhir.util.ReflectionUtil;
 import org.apache.commons.lang3.Validate;
 import org.hl7.fhir.instance.model.api.*;
@@ -68,17 +69,23 @@ public class OperationParameter implements IParameter {
 	private Class<?> myParameterType;
 	private String myParamType;
 	private SearchParameter mySearchParameterBinding;
+	private String myDescription;
+	private List<String> myExampleValues;
 
-	public OperationParameter(FhirContext theCtx, String theOperationName, OperationParam theOperationParam) {
-		this(theCtx, theOperationName, theOperationParam.name(), theOperationParam.min(), theOperationParam.max());
-	}
-
-	OperationParameter(FhirContext theCtx, String theOperationName, String theParameterName, int theMin, int theMax) {
+	OperationParameter(FhirContext theCtx, String theOperationName, String theParameterName, int theMin, int theMax, String theDescription, List<String> theExampleValues) {
 		myOperationName = theOperationName;
 		myName = theParameterName;
 		myMin = theMin;
 		myMax = theMax;
 		myContext = theCtx;
+		myDescription = theDescription;
+
+		List<String> exampleValues = new ArrayList<>();
+		if (theExampleValues != null) {
+			exampleValues.addAll(theExampleValues);
+		}
+		myExampleValues = Collections.unmodifiableList(exampleValues);
+
 	}
 
 	@SuppressWarnings({"rawtypes", "unchecked"})
@@ -438,6 +445,14 @@ public class OperationParameter implements IParameter {
 		}
 	}
 
+	public String getDescription() {
+		return myDescription;
+	}
+
+	public List<String> getExampleValues() {
+		return myExampleValues;
+	}
+
 	interface IOperationParamConverter {
 
 		Object incomingServer(Object theObject);
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java
index cdb569d43e4..233414fcabc 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java
@@ -35,6 +35,7 @@ import ca.uhn.fhir.rest.param.ParameterUtil;
 import ca.uhn.fhir.rest.param.QualifierDetails;
 import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
 import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.util.ParametersUtil;
 import org.apache.commons.lang3.StringUtils;
 import org.hl7.fhir.instance.model.api.IAnyResource;
 import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -80,15 +81,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
 		this.myCompartmentName = StringUtils.defaultIfBlank(search.compartmentName(), null);
 		this.myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext());
 		this.myAllowUnknownParams = search.allowUnknownParams();
-
-		Description desc = theMethod.getAnnotation(Description.class);
-		if (desc != null) {
-			if (isNotBlank(desc.formalDefinition())) {
-				myDescription = StringUtils.defaultIfBlank(desc.formalDefinition(), null);
-			} else {
-				myDescription = StringUtils.defaultIfBlank(desc.shortDefinition(), null);
-			}
-		}
+		this.myDescription = ParametersUtil.extractDescription(theMethod);
 
 		/*
 		 * Only compartment searching methods may have an ID parameter
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ValidateMethodBindingDstu2Plus.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ValidateMethodBindingDstu2Plus.java
index ee7cf623818..3568ac9e345 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ValidateMethodBindingDstu2Plus.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ValidateMethodBindingDstu2Plus.java
@@ -20,10 +20,12 @@ package ca.uhn.fhir.rest.server.method;
  * #L%
  */
 
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 
+import ca.uhn.fhir.util.ParametersUtil;
 import org.hl7.fhir.instance.model.api.IBaseResource;
 
 import ca.uhn.fhir.context.FhirContext;
@@ -37,9 +39,9 @@ public class ValidateMethodBindingDstu2Plus extends OperationMethodBinding {
 
 	public ValidateMethodBindingDstu2Plus(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
 			Validate theAnnotation) {
-		super(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, true, Constants.EXTOP_VALIDATE, theAnnotation.type(), null, new OperationParam[0], BundleTypeEnum.COLLECTION);
+		super(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, true, Constants.EXTOP_VALIDATE, theAnnotation.type(), null, new OperationParam[0], BundleTypeEnum.COLLECTION, false);
 
-		List<IParameter> newParams = new ArrayList<IParameter>();
+		List<IParameter> newParams = new ArrayList<>();
 		int idx = 0;
 		for (IParameter next : getParameters()) {
 			if (next instanceof ResourceParameter) {
@@ -48,7 +50,10 @@ public class ValidateMethodBindingDstu2Plus extends OperationMethodBinding {
 					if (String.class.equals(parameterType) || EncodingEnum.class.equals(parameterType)) {
 						newParams.add(next);
 					} else {
-						OperationParameter parameter = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_RESOURCE, 0, 1);
+						Annotation[] parameterAnnotations = theMethod.getParameterAnnotations()[idx];
+						String description = ParametersUtil.extractDescription(parameterAnnotations);
+						List<String> examples = ParametersUtil.extractExamples(parameterAnnotations);
+						OperationParameter parameter = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_RESOURCE, 0, 1, description, examples);
 						parameter.initializeTypes(theMethod, null, null, parameterType);
 						newParams.add(parameter);
 					}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.java
index 230763bc8a6..d856dfd9598 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.java
@@ -27,16 +27,22 @@ import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
 import ca.uhn.fhir.rest.server.method.SearchParameter;
 import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
 import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
+import ca.uhn.fhir.util.ExtensionUtil;
 import ca.uhn.fhir.util.FhirTerser;
+import ca.uhn.fhir.util.HapiExtensions;
 import com.google.common.collect.TreeMultimap;
+import org.apache.commons.text.WordUtils;
 import org.hl7.fhir.instance.model.api.IBase;
 import org.hl7.fhir.instance.model.api.IBaseConformance;
+import org.hl7.fhir.instance.model.api.IBaseExtension;
+import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
 import org.hl7.fhir.instance.model.api.IBaseResource;
 import org.hl7.fhir.instance.model.api.IIdType;
 import org.hl7.fhir.instance.model.api.IPrimitiveType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.annotation.Nonnull;
 import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServletRequest;
 import java.util.Date;
@@ -83,641 +89,758 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
  */
 public class ServerCapabilityStatementProvider implements IServerConformanceProvider<IBaseConformance> {
 
-    public static final boolean DEFAULT_REST_RESOURCE_REV_INCLUDES_ENABLED = true;
-    private static final Logger ourLog = LoggerFactory.getLogger(ServerCapabilityStatementProvider.class);
-    private final FhirContext myContext;
-    private final RestfulServer myServer;
-    private final ISearchParamRegistry mySearchParamRegistry;
-    private final RestfulServerConfiguration myServerConfiguration;
-    private final IValidationSupport myValidationSupport;
-    private String myPublisher = "Not provided";
-    private boolean myRestResourceRevIncludesEnabled = DEFAULT_REST_RESOURCE_REV_INCLUDES_ENABLED;
-
-    /**
-     * Constructor
-     */
-    public ServerCapabilityStatementProvider(RestfulServer theServer) {
-        myServer = theServer;
-        myContext = theServer.getFhirContext();
-        mySearchParamRegistry = null;
-        myServerConfiguration = null;
-        myValidationSupport = null;
-    }
-
-    /**
-     * Constructor
-     */
-    public ServerCapabilityStatementProvider(FhirContext theContext, RestfulServerConfiguration theServerConfiguration) {
-        myContext = theContext;
-        myServerConfiguration = theServerConfiguration;
-        mySearchParamRegistry = null;
-        myServer = null;
-        myValidationSupport = null;
-    }
-
-    /**
-     * Constructor
-     */
-    public ServerCapabilityStatementProvider(RestfulServer theRestfulServer, ISearchParamRegistry theSearchParamRegistry, IValidationSupport theValidationSupport) {
-        myContext = theRestfulServer.getFhirContext();
-        mySearchParamRegistry = theSearchParamRegistry;
-        myServer = theRestfulServer;
-        myServerConfiguration = null;
-        myValidationSupport = theValidationSupport;
-    }
-
-    private void checkBindingForSystemOps(FhirTerser theTerser, IBase theRest, Set<String> theSystemOps, BaseMethodBinding<?> theMethodBinding) {
-        RestOperationTypeEnum restOperationType = theMethodBinding.getRestOperationType();
-        if (restOperationType.isSystemLevel()) {
-            String sysOp = restOperationType.getCode();
-            if (theSystemOps.contains(sysOp) == false) {
-                theSystemOps.add(sysOp);
-                IBase interaction = theTerser.addElement(theRest, "interaction");
-                theTerser.addElement(interaction, "code", sysOp);
-            }
-        }
-    }
-
-
-    private String conformanceDate(RestfulServerConfiguration theServerConfiguration) {
-        IPrimitiveType<Date> buildDate = theServerConfiguration.getConformanceDate();
-        if (buildDate != null && buildDate.getValue() != null) {
-            try {
-                return buildDate.getValueAsString();
-            } catch (DataFormatException e) {
-                // fall through
-            }
-        }
-        return InstantDt.withCurrentTime().getValueAsString();
-    }
-
-    private RestfulServerConfiguration getServerConfiguration() {
-        if (myServer != null) {
-            return myServer.createConfiguration();
-        }
-        return myServerConfiguration;
-    }
-
-
-    /**
-     * Gets the value of the "publisher" that will be placed in the generated conformance statement. As this is a mandatory element, the value should not be null (although this is not enforced). The
-     * value defaults to "Not provided" but may be set to null, which will cause this element to be omitted.
-     */
-    public String getPublisher() {
-        return myPublisher;
-    }
-
-    /**
-     * Sets the value of the "publisher" that will be placed in the generated conformance statement. As this is a mandatory element, the value should not be null (although this is not enforced). The
-     * value defaults to "Not provided" but may be set to null, which will cause this element to be omitted.
-     */
-    public void setPublisher(String thePublisher) {
-        myPublisher = thePublisher;
-    }
-
-    @Override
-    @Metadata
-    public IBaseConformance getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
-
-        HttpServletRequest servletRequest = null;
-        if (theRequestDetails instanceof ServletRequestDetails) {
-            servletRequest = ((ServletRequestDetails) theRequestDetails).getServletRequest();
-        }
-
-        RestfulServerConfiguration configuration = getServerConfiguration();
-        Bindings bindings = configuration.provideBindings();
-
-        IBaseConformance retVal = (IBaseConformance) myContext.getResourceDefinition("CapabilityStatement").newInstance();
-
-        FhirTerser terser = myContext.newTerser();
-
-        TreeMultimap<String, String> resourceTypeToSupportedProfiles = getSupportedProfileMultimap(terser);
-
-        terser.addElement(retVal, "id", UUID.randomUUID().toString());
-        terser.addElement(retVal, "name", "RestServer");
-        terser.addElement(retVal, "publisher", myPublisher);
-        terser.addElement(retVal, "date", conformanceDate(configuration));
-        terser.addElement(retVal, "fhirVersion", myContext.getVersion().getVersion().getFhirVersionString());
-
-        ServletContext servletContext = (ServletContext) (theRequest == null ? null : theRequest.getAttribute(RestfulServer.SERVLET_CONTEXT_ATTRIBUTE));
-        String serverBase = configuration.getServerAddressStrategy().determineServerBase(servletContext, theRequest);
-        terser.addElement(retVal, "implementation.url", serverBase);
-        terser.addElement(retVal, "implementation.description", configuration.getImplementationDescription());
-        terser.addElement(retVal, "kind", "instance");
-        terser.addElement(retVal, "software.name", configuration.getServerName());
-        terser.addElement(retVal, "software.version", configuration.getServerVersion());
-        if (myContext.isFormatXmlSupported()) {
-            terser.addElement(retVal, "format", Constants.CT_FHIR_XML_NEW);
-            terser.addElement(retVal, "format", Constants.FORMAT_XML);
-        }
-        if (myContext.isFormatJsonSupported()) {
-            terser.addElement(retVal, "format", Constants.CT_FHIR_JSON_NEW);
-            terser.addElement(retVal, "format", Constants.FORMAT_JSON);
-        }
-        if (myContext.isFormatRdfSupported()) {
-            terser.addElement(retVal, "format", Constants.CT_RDF_TURTLE);
-            terser.addElement(retVal, "format", Constants.FORMAT_TURTLE);
-        }
-        terser.addElement(retVal, "status", "active");
-
-        IBase rest = terser.addElement(retVal, "rest");
-        terser.addElement(rest, "mode", "server");
-
-        Set<String> systemOps = new HashSet<>();
-        Set<String> operationNames = new HashSet<>();
-
-        Map<String, List<BaseMethodBinding<?>>> resourceToMethods = configuration.collectMethodBindings();
-        Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype = configuration.getNameToSharedSupertype();
-
-        TreeMultimap<String, String> resourceNameToIncludes = TreeMultimap.create();
-        TreeMultimap<String, String> resourceNameToRevIncludes = TreeMultimap.create();
-        for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
-            String resourceName = nextEntry.getKey();
-            for (BaseMethodBinding<?> nextMethod : nextEntry.getValue()) {
-                if (nextMethod instanceof SearchMethodBinding) {
-                    resourceNameToIncludes.putAll(resourceName, nextMethod.getIncludes());
-                    resourceNameToRevIncludes.putAll(resourceName, nextMethod.getRevIncludes());
-                }
-            }
-
-        }
-
-        for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
-
-            String resourceName = nextEntry.getKey();
-            if (nextEntry.getKey().isEmpty() == false) {
-                Set<String> resourceOps = new HashSet<>();
-                IBase resource = terser.addElement(rest, "resource");
-
-                postProcessRestResource(terser, resource, resourceName);
-
-                RuntimeResourceDefinition def;
-                FhirContext context = configuration.getFhirContext();
-                if (resourceNameToSharedSupertype.containsKey(resourceName)) {
-                    def = context.getResourceDefinition(resourceNameToSharedSupertype.get(resourceName));
-                } else {
-                    def = context.getResourceDefinition(resourceName);
-                }
-                terser.addElement(resource, "type", def.getName());
-                terser.addElement(resource, "profile", def.getResourceProfile(serverBase));
-
-                for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
-                    RestOperationTypeEnum resOpCode = nextMethodBinding.getRestOperationType();
-                    if (resOpCode.isTypeLevel() || resOpCode.isInstanceLevel()) {
-                        String resOp;
-                        resOp = resOpCode.getCode();
-                        if (resourceOps.contains(resOp) == false) {
-                            resourceOps.add(resOp);
-                            IBase interaction = terser.addElement(resource, "interaction");
-                            terser.addElement(interaction, "code", resOp);
-                        }
-                        if (RestOperationTypeEnum.VREAD.equals(resOpCode)) {
-                            // vread implies read
-                            resOp = "read";
-                            if (resourceOps.contains(resOp) == false) {
-                                resourceOps.add(resOp);
-                                IBase interaction = terser.addElement(resource, "interaction");
-                                terser.addElement(interaction, "code", resOp);
-                            }
-                        }
-                    }
-
-                    if (nextMethodBinding.isSupportsConditional()) {
-                        switch (resOpCode) {
-                            case CREATE:
-                                terser.setElement(resource, "conditionalCreate", "true");
-                                break;
-                            case DELETE:
-                                if (nextMethodBinding.isSupportsConditionalMultiple()) {
-                                    terser.setElement(resource, "conditionalDelete", "multiple");
-                                } else {
-                                    terser.setElement(resource, "conditionalDelete", "single");
-                                }
-                                break;
-                            case UPDATE:
-                                terser.setElement(resource, "conditionalUpdate", "true");
-                                break;
-                            case HISTORY_INSTANCE:
-                            case HISTORY_SYSTEM:
-                            case HISTORY_TYPE:
-                            case READ:
-                            case SEARCH_SYSTEM:
-                            case SEARCH_TYPE:
-                            case TRANSACTION:
-                            case VALIDATE:
-                            case VREAD:
-                            case METADATA:
-                            case META_ADD:
-                            case META:
-                            case META_DELETE:
-                            case PATCH:
-                            case BATCH:
-                            case ADD_TAGS:
-                            case DELETE_TAGS:
-                            case GET_TAGS:
-                            case GET_PAGE:
-                            case GRAPHQL_REQUEST:
-                            case EXTENDED_OPERATION_SERVER:
-                            case EXTENDED_OPERATION_TYPE:
-                            case EXTENDED_OPERATION_INSTANCE:
-                            default:
-                                break;
-                        }
-                    }
-
-                    checkBindingForSystemOps(terser, rest, systemOps, nextMethodBinding);
-
-                    if (nextMethodBinding instanceof SearchMethodBinding) {
-                        SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding;
-                        if (methodBinding.getQueryName() != null) {
-                            String queryName = bindings.getNamedSearchMethodBindingToName().get(methodBinding);
-                            if (operationNames.add(queryName)) {
-                                IBase operation = terser.addElement(rest, "operation");
-                                terser.addElement(operation, "name", methodBinding.getQueryName());
-                                terser.addElement(operation, "definition", (getOperationDefinitionPrefix(theRequestDetails) + "OperationDefinition/" + queryName));
-                            }
-                        }
-                    } else if (nextMethodBinding instanceof OperationMethodBinding) {
-                        OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
-                        String opName = bindings.getOperationBindingToName().get(methodBinding);
-                        // Only add each operation (by name) once
-                        if (operationNames.add(opName)) {
-                            IBase operation = terser.addElement(rest, "operation");
-                            terser.addElement(operation, "name", methodBinding.getName().substring(1));
-                            terser.addElement(operation, "definition", getOperationDefinitionPrefix(theRequestDetails) + "OperationDefinition/" + opName);
-                        }
-                    }
-
-                }
-
-
-                ISearchParamRegistry serverConfiguration;
-                if (myServerConfiguration != null) {
-                    serverConfiguration = myServerConfiguration;
-                } else {
-                    serverConfiguration = myServer.createConfiguration();
-                }
-
-                /*
-                 * If we have an explicit registry (which will be the case in the JPA server) we use it as priority,
-                 * but also fill in any gaps using params from the server itself. This makes sure we include
-                 * global params like _lastUpdated
-                 */
-                Map<String, RuntimeSearchParam> searchParams;
-                ISearchParamRegistry searchParamRegistry;
-                if (mySearchParamRegistry != null) {
-                    searchParamRegistry = mySearchParamRegistry;
-                    searchParams = new HashMap<>(mySearchParamRegistry.getActiveSearchParams(resourceName));
-                    for (Entry<String, RuntimeSearchParam> nextBuiltInSp : serverConfiguration.getActiveSearchParams(resourceName).entrySet()) {
-                        if (nextBuiltInSp.getKey().startsWith("_") && !searchParams.containsKey(nextBuiltInSp.getKey())) {
-                            searchParams.put(nextBuiltInSp.getKey(), nextBuiltInSp.getValue());
-                        }
-                    }
-                } else {
-                    searchParamRegistry = serverConfiguration;
-                    searchParams = serverConfiguration.getActiveSearchParams(resourceName);
-                }
-
-
-                for (RuntimeSearchParam next : searchParams.values()) {
-                    IBase searchParam = terser.addElement(resource, "searchParam");
-                    terser.addElement(searchParam, "name", next.getName());
-                    terser.addElement(searchParam, "type", next.getParamType().getCode());
-                    if (isNotBlank(next.getDescription())) {
-                        terser.addElement(searchParam, "documentation", next.getDescription());
-                    }
-
-                    String spUri = next.getUri();
-                    if (isBlank(spUri) && servletRequest != null) {
-                        String id;
-                        if (next.getId() != null) {
-                            id = next.getId().toUnqualifiedVersionless().getValue();
-                        } else {
-                            id = resourceName + "-" + next.getName();
-                        }
-                        spUri = configuration.getServerAddressStrategy().determineServerBase(servletRequest.getServletContext(), servletRequest) + "/" + id;
-                    }
-                    if (isNotBlank(spUri)) {
-                        terser.addElement(searchParam, "definition", spUri);
-                    }
-                }
-
-                // Add Include to CapabilityStatement.rest.resource
-                NavigableSet<String> resourceIncludes = resourceNameToIncludes.get(resourceName);
-                if (resourceIncludes.isEmpty()) {
-                    List<String> includes = searchParams
-                            .values()
-                            .stream()
-                            .filter(t -> t.getParamType() == RestSearchParameterTypeEnum.REFERENCE)
-                            .map(t -> resourceName + ":" + t.getName())
-                            .sorted()
-                            .collect(Collectors.toList());
-                    terser.addElement(resource, "searchInclude", "*");
-                    for (String nextInclude : includes) {
-                        terser.addElement(resource, "searchInclude", nextInclude);
-                    }
-                } else {
-                    for (String resourceInclude : resourceIncludes) {
-                        terser.addElement(resource, "searchInclude", resourceInclude);
-                    }
-                }
-
-                // Add RevInclude to CapabilityStatement.rest.resource
-                if (myRestResourceRevIncludesEnabled) {
-                    NavigableSet<String> resourceRevIncludes = resourceNameToRevIncludes.get(resourceName);
-                    if (resourceRevIncludes.isEmpty()) {
-                        TreeSet<String> revIncludes = new TreeSet<>();
-                        for (String nextResourceName : resourceToMethods.keySet()) {
-                            if (isBlank(nextResourceName)) {
-                                continue;
-                            }
-
-                            for (RuntimeSearchParam t : searchParamRegistry.getActiveSearchParams(nextResourceName).values()) {
-                                if (t.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
-                                    if (isNotBlank(t.getName())) {
-                                        boolean appropriateTarget = false;
-                                        if (t.getTargets().contains(resourceName) || t.getTargets().isEmpty()) {
-                                            appropriateTarget = true;
-                                        }
-
-                                        if (appropriateTarget) {
-                                            revIncludes.add(nextResourceName + ":" + t.getName());
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                        for (String nextInclude : revIncludes) {
-                            terser.addElement(resource, "searchRevInclude", nextInclude);
-                        }
-                    } else {
-                        for (String resourceInclude : resourceRevIncludes) {
-                            terser.addElement(resource, "searchRevInclude", resourceInclude);
-                        }
-                    }
-                }
-
-                // Add SupportedProfile to CapabilityStatement.rest.resource
-                for (String supportedProfile : resourceTypeToSupportedProfiles.get(resourceName)) {
-                    terser.addElement(resource, "supportedProfile", supportedProfile);
-                }
-
-            } else {
-                for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
-                    checkBindingForSystemOps(terser, rest, systemOps, nextMethodBinding);
-                    if (nextMethodBinding instanceof OperationMethodBinding) {
-                        OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
-                        String opName = bindings.getOperationBindingToName().get(methodBinding);
-                        if (operationNames.add(opName)) {
-                            ourLog.debug("Found bound operation: {}", opName);
-                            IBase operation = terser.addElement(rest, "operation");
-                            terser.addElement(operation, "name", methodBinding.getName().substring(1));
-                            terser.addElement(operation, "definition", getOperationDefinitionPrefix(theRequestDetails) + "OperationDefinition/" + opName);
-                        }
-                    }
-                }
-            }
-
-            postProcessRest(terser, rest);
-
-        }
-
-        postProcess(terser, retVal);
-
-        return retVal;
-    }
-
-    private TreeMultimap<String, String> getSupportedProfileMultimap(FhirTerser terser) {
-        TreeMultimap<String, String> resourceTypeToSupportedProfiles = TreeMultimap.create();
-        if (myValidationSupport != null) {
-            List<IBaseResource> allStructureDefinitions = myValidationSupport.fetchAllNonBaseStructureDefinitions();
-            if (allStructureDefinitions != null) {
-                for (IBaseResource next : allStructureDefinitions) {
-                    String kind = terser.getSinglePrimitiveValueOrNull(next, "kind");
-                    String url = terser.getSinglePrimitiveValueOrNull(next, "url");
-                    String baseDefinition = defaultString(terser.getSinglePrimitiveValueOrNull(next, "baseDefinition"));
-                    if ("resource".equals(kind) && isNotBlank(url)) {
-
-                        // Don't include the base resource definitions in the supported profile list - This isn't helpful
-                        if (baseDefinition.equals("http://hl7.org/fhir/StructureDefinition/DomainResource") || baseDefinition.equals("http://hl7.org/fhir/StructureDefinition/Resource")) {
-                            continue;
-                        }
-
-                        String resourceType = terser.getSinglePrimitiveValueOrNull(next, "snapshot.element.path");
-                        if (isBlank(resourceType)) {
-                            resourceType = terser.getSinglePrimitiveValueOrNull(next, "differential.element.path");
-                        }
-
-                        if (isNotBlank(resourceType)) {
-                            resourceTypeToSupportedProfiles.put(resourceType, url);
-                        }
-                    }
-                }
-            }
-        }
-        return resourceTypeToSupportedProfiles;
-    }
-
-    /**
-     * Subclasses may override
-     */
-    protected void postProcess(FhirTerser theTerser, IBaseConformance theCapabilityStatement) {
-        // nothing
-    }
-
-    /**
-     * Subclasses may override
-     */
-    protected void postProcessRest(FhirTerser theTerser, IBase theRest) {
-        // nothing
-    }
-
-    /**
-     * Subclasses may override
-     */
-    protected void postProcessRestResource(FhirTerser theTerser, IBase theResource, String theResourceName) {
-        // nothing
-    }
-
-    protected String getOperationDefinitionPrefix(RequestDetails theRequestDetails) {
-        if (theRequestDetails == null) {
-            return "";
-        }
-        return theRequestDetails.getServerBaseForRequest() + "/";
-    }
-
-
-    @Read(typeName = "OperationDefinition")
-    public IBaseResource readOperationDefinition(@IdParam IIdType theId, RequestDetails theRequestDetails) {
-        if (theId == null || theId.hasIdPart() == false) {
-            throw new ResourceNotFoundException(theId);
-        }
-        RestfulServerConfiguration configuration = getServerConfiguration();
-        Bindings bindings = configuration.provideBindings();
-
-        List<OperationMethodBinding> operationBindings = bindings.getOperationNameToBindings().get(theId.getIdPart());
-        if (operationBindings != null && !operationBindings.isEmpty()) {
-            return readOperationDefinitionForOperation(operationBindings);
-        }
-        List<SearchMethodBinding> searchBindings = bindings.getSearchNameToBindings().get(theId.getIdPart());
-        if (searchBindings != null && !searchBindings.isEmpty()) {
-            return readOperationDefinitionForNamedSearch(searchBindings);
-        }
-        throw new ResourceNotFoundException(theId);
-    }
-
-    private IBaseResource readOperationDefinitionForNamedSearch(List<SearchMethodBinding> bindings) {
-        IBaseResource op = myContext.getResourceDefinition("OperationDefinition").newInstance();
-        FhirTerser terser = myContext.newTerser();
-
-        terser.addElement(op, "status", "active");
-        terser.addElement(op, "kind", "query");
-        terser.addElement(op, "affectsState", "false");
-
-        terser.addElement(op, "instance", "false");
-
-        Set<String> inParams = new HashSet<>();
-
-        String operationCode = null;
-        for (SearchMethodBinding binding : bindings) {
-            if (isNotBlank(binding.getDescription())) {
-                terser.addElement(op, "description", binding.getDescription());
-            }
-            if (isBlank(binding.getResourceProviderResourceName())) {
-                terser.addElement(op, "system", "true");
-                terser.addElement(op, "type", "false");
-            } else {
-                terser.addElement(op, "system", "false");
-                terser.addElement(op, "type", "true");
-                terser.addElement(op, "resource", binding.getResourceProviderResourceName());
-            }
-
-            if (operationCode == null) {
-                operationCode = binding.getQueryName();
-            }
-
-            for (IParameter nextParamUntyped : binding.getParameters()) {
-                if (nextParamUntyped instanceof SearchParameter) {
-                    SearchParameter nextParam = (SearchParameter) nextParamUntyped;
-                    if (!inParams.add(nextParam.getName())) {
-                        continue;
-                    }
-
-                    IBase param = terser.addElement(op, "parameter");
-                    terser.addElement(param, "use", "in");
-                    terser.addElement(param, "type", "string");
-                    terser.addElement(param, "searchType", nextParam.getParamType().getCode());
-                    terser.addElement(param, "min", nextParam.isRequired() ? "1" : "0");
-                    terser.addElement(param, "max", "1");
-                    terser.addElement(param, "name", nextParam.getName());
-                }
-            }
-
-        }
-
-        terser.addElement(op, "code", operationCode);
-        terser.addElement(op, "name", "Search_" + operationCode);
-
-        return op;
-    }
-
-    private IBaseResource readOperationDefinitionForOperation(List<OperationMethodBinding> bindings) {
-        IBaseResource op = myContext.getResourceDefinition("OperationDefinition").newInstance();
-        FhirTerser terser = myContext.newTerser();
-
-        terser.addElement(op, "status", "active");
-        terser.addElement(op, "kind", "operation");
-
-        boolean systemLevel = false;
-        boolean typeLevel = false;
-        boolean instanceLevel = false;
-        boolean affectsState = false;
-        String description = null;
-        String code = null;
-        String name;
-
-        Set<String> resourceNames = new TreeSet<>();
-        Set<String> inParams = new HashSet<>();
-        Set<String> outParams = new HashSet<>();
-
-        for (OperationMethodBinding sharedDescription : bindings) {
-            if (isNotBlank(sharedDescription.getDescription()) && isBlank(description)) {
-                description = sharedDescription.getDescription();
-            }
-            if (sharedDescription.isCanOperateAtInstanceLevel()) {
-                instanceLevel = true;
-            }
-            if (sharedDescription.isCanOperateAtServerLevel()) {
-                systemLevel = true;
-            }
-            if (sharedDescription.isCanOperateAtTypeLevel()) {
-                typeLevel = true;
-            }
-            if (!sharedDescription.isIdempotent()) {
-                affectsState |= true;
-            }
-
-            code = sharedDescription.getName().substring(1);
-
-            if (isNotBlank(sharedDescription.getResourceName())) {
-                resourceNames.add(sharedDescription.getResourceName());
-            }
-
-            for (IParameter nextParamUntyped : sharedDescription.getParameters()) {
-                if (nextParamUntyped instanceof OperationParameter) {
-                    OperationParameter nextParam = (OperationParameter) nextParamUntyped;
-                    if (!inParams.add(nextParam.getName())) {
-                        continue;
-                    }
-                    IBase param = terser.addElement(op, "parameter");
-                    terser.addElement(param, "use", "in");
-                    if (nextParam.getParamType() != null) {
-                        terser.addElement(param, "type", nextParam.getParamType());
-                    }
-                    if (nextParam.getSearchParamType() != null) {
-                        terser.addElement(param, "searchType", nextParam.getSearchParamType());
-                    }
-                    terser.addElement(param, "min", Integer.toString(nextParam.getMin()));
-                    terser.addElement(param, "max", (nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax())));
-                    terser.addElement(param, "name", nextParam.getName());
-                }
-            }
-
-            for (ReturnType nextParam : sharedDescription.getReturnParams()) {
-                if (!outParams.add(nextParam.getName())) {
-                    continue;
-                }
-                IBase param = terser.addElement(op, "parameter");
-                terser.addElement(param, "use", "out");
-                if (nextParam.getType() != null) {
-                    terser.addElement(param, "type", nextParam.getType());
-                }
-                terser.addElement(param, "min", Integer.toString(nextParam.getMin()));
-                terser.addElement(param, "max", (nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax())));
-                terser.addElement(param, "name", nextParam.getName());
-            }
-        }
-
-        name = "Operation_" + code;
-
-        terser.addElements(op, "resource", resourceNames);
-        terser.addElement(op, "name", name);
-        terser.addElement(op, "code", code);
-        terser.addElement(op, "description", description);
-        terser.addElement(op, "affectsState", Boolean.toString(affectsState));
-        terser.addElement(op, "system", Boolean.toString(systemLevel));
-        terser.addElement(op, "type", Boolean.toString(typeLevel));
-        terser.addElement(op, "instance", Boolean.toString(instanceLevel));
-
-        return op;
-    }
-
-    @Override
-    public void setRestfulServer(RestfulServer theRestfulServer) {
-        // ignore
-    }
-
-    public void setRestResourceRevIncludesEnabled(boolean theRestResourceRevIncludesEnabled) {
-        myRestResourceRevIncludesEnabled = theRestResourceRevIncludesEnabled;
-    }
+	public static final boolean DEFAULT_REST_RESOURCE_REV_INCLUDES_ENABLED = true;
+	private static final Logger ourLog = LoggerFactory.getLogger(ServerCapabilityStatementProvider.class);
+	private final FhirContext myContext;
+	private final RestfulServer myServer;
+	private final ISearchParamRegistry mySearchParamRegistry;
+	private final RestfulServerConfiguration myServerConfiguration;
+	private final IValidationSupport myValidationSupport;
+	private String myPublisher = "Not provided";
+	private boolean myRestResourceRevIncludesEnabled = DEFAULT_REST_RESOURCE_REV_INCLUDES_ENABLED;
+
+	/**
+	 * Constructor
+	 */
+	public ServerCapabilityStatementProvider(RestfulServer theServer) {
+		myServer = theServer;
+		myContext = theServer.getFhirContext();
+		mySearchParamRegistry = null;
+		myServerConfiguration = null;
+		myValidationSupport = null;
+	}
+
+	/**
+	 * Constructor
+	 */
+	public ServerCapabilityStatementProvider(FhirContext theContext, RestfulServerConfiguration theServerConfiguration) {
+		myContext = theContext;
+		myServerConfiguration = theServerConfiguration;
+		mySearchParamRegistry = null;
+		myServer = null;
+		myValidationSupport = null;
+	}
+
+	/**
+	 * Constructor
+	 */
+	public ServerCapabilityStatementProvider(RestfulServer theRestfulServer, ISearchParamRegistry theSearchParamRegistry, IValidationSupport theValidationSupport) {
+		myContext = theRestfulServer.getFhirContext();
+		mySearchParamRegistry = theSearchParamRegistry;
+		myServer = theRestfulServer;
+		myServerConfiguration = null;
+		myValidationSupport = theValidationSupport;
+	}
+
+	private void checkBindingForSystemOps(FhirTerser theTerser, IBase theRest, Set<String> theSystemOps, BaseMethodBinding<?> theMethodBinding) {
+		RestOperationTypeEnum restOperationType = theMethodBinding.getRestOperationType();
+		if (restOperationType.isSystemLevel()) {
+			String sysOp = restOperationType.getCode();
+			if (theSystemOps.contains(sysOp) == false) {
+				theSystemOps.add(sysOp);
+				IBase interaction = theTerser.addElement(theRest, "interaction");
+				theTerser.addElement(interaction, "code", sysOp);
+			}
+		}
+	}
+
+
+	private String conformanceDate(RestfulServerConfiguration theServerConfiguration) {
+		IPrimitiveType<Date> buildDate = theServerConfiguration.getConformanceDate();
+		if (buildDate != null && buildDate.getValue() != null) {
+			try {
+				return buildDate.getValueAsString();
+			} catch (DataFormatException e) {
+				// fall through
+			}
+		}
+		return InstantDt.withCurrentTime().getValueAsString();
+	}
+
+	private RestfulServerConfiguration getServerConfiguration() {
+		if (myServer != null) {
+			return myServer.createConfiguration();
+		}
+		return myServerConfiguration;
+	}
+
+
+	/**
+	 * Gets the value of the "publisher" that will be placed in the generated conformance statement. As this is a mandatory element, the value should not be null (although this is not enforced). The
+	 * value defaults to "Not provided" but may be set to null, which will cause this element to be omitted.
+	 */
+	public String getPublisher() {
+		return myPublisher;
+	}
+
+	/**
+	 * Sets the value of the "publisher" that will be placed in the generated conformance statement. As this is a mandatory element, the value should not be null (although this is not enforced). The
+	 * value defaults to "Not provided" but may be set to null, which will cause this element to be omitted.
+	 */
+	public void setPublisher(String thePublisher) {
+		myPublisher = thePublisher;
+	}
+
+	@Override
+	@Metadata
+	public IBaseConformance getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
+
+		HttpServletRequest servletRequest = null;
+		if (theRequestDetails instanceof ServletRequestDetails) {
+			servletRequest = ((ServletRequestDetails) theRequestDetails).getServletRequest();
+		}
+
+		RestfulServerConfiguration configuration = getServerConfiguration();
+		Bindings bindings = configuration.provideBindings();
+
+		IBaseConformance retVal = (IBaseConformance) myContext.getResourceDefinition("CapabilityStatement").newInstance();
+
+		FhirTerser terser = myContext.newTerser();
+
+		TreeMultimap<String, String> resourceTypeToSupportedProfiles = getSupportedProfileMultimap(terser);
+
+		terser.addElement(retVal, "id", UUID.randomUUID().toString());
+		terser.addElement(retVal, "name", "RestServer");
+		terser.addElement(retVal, "publisher", myPublisher);
+		terser.addElement(retVal, "date", conformanceDate(configuration));
+		terser.addElement(retVal, "fhirVersion", myContext.getVersion().getVersion().getFhirVersionString());
+
+		ServletContext servletContext = (ServletContext) (theRequest == null ? null : theRequest.getAttribute(RestfulServer.SERVLET_CONTEXT_ATTRIBUTE));
+		String serverBase = configuration.getServerAddressStrategy().determineServerBase(servletContext, theRequest);
+		terser.addElement(retVal, "implementation.url", serverBase);
+		terser.addElement(retVal, "implementation.description", configuration.getImplementationDescription());
+		terser.addElement(retVal, "kind", "instance");
+		if (myServer != null && isNotBlank(myServer.getCopyright())) {
+			terser.addElement(retVal, "copyright", myServer.getCopyright());
+		}
+		terser.addElement(retVal, "software.name", configuration.getServerName());
+		terser.addElement(retVal, "software.version", configuration.getServerVersion());
+		if (myContext.isFormatXmlSupported()) {
+			terser.addElement(retVal, "format", Constants.CT_FHIR_XML_NEW);
+			terser.addElement(retVal, "format", Constants.FORMAT_XML);
+		}
+		if (myContext.isFormatJsonSupported()) {
+			terser.addElement(retVal, "format", Constants.CT_FHIR_JSON_NEW);
+			terser.addElement(retVal, "format", Constants.FORMAT_JSON);
+		}
+		if (myContext.isFormatRdfSupported()) {
+			terser.addElement(retVal, "format", Constants.CT_RDF_TURTLE);
+			terser.addElement(retVal, "format", Constants.FORMAT_TURTLE);
+		}
+		terser.addElement(retVal, "status", "active");
+
+		IBase rest = terser.addElement(retVal, "rest");
+		terser.addElement(rest, "mode", "server");
+
+		Set<String> systemOps = new HashSet<>();
+
+		Map<String, List<BaseMethodBinding<?>>> resourceToMethods = configuration.collectMethodBindings();
+		Map<String, Class<? extends IBaseResource>> resourceNameToSharedSupertype = configuration.getNameToSharedSupertype();
+		List<BaseMethodBinding<?>> globalMethodBindings = configuration.getGlobalBindings();
+
+		TreeMultimap<String, String> resourceNameToIncludes = TreeMultimap.create();
+		TreeMultimap<String, String> resourceNameToRevIncludes = TreeMultimap.create();
+		for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
+			String resourceName = nextEntry.getKey();
+			for (BaseMethodBinding<?> nextMethod : nextEntry.getValue()) {
+				if (nextMethod instanceof SearchMethodBinding) {
+					resourceNameToIncludes.putAll(resourceName, nextMethod.getIncludes());
+					resourceNameToRevIncludes.putAll(resourceName, nextMethod.getRevIncludes());
+				}
+			}
+
+		}
+
+		for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
+
+			Set<String> operationNames = new HashSet<>();
+			String resourceName = nextEntry.getKey();
+			if (nextEntry.getKey().isEmpty() == false) {
+				Set<String> resourceOps = new HashSet<>();
+				IBase resource = terser.addElement(rest, "resource");
+
+				postProcessRestResource(terser, resource, resourceName);
+
+				RuntimeResourceDefinition def;
+				FhirContext context = configuration.getFhirContext();
+				if (resourceNameToSharedSupertype.containsKey(resourceName)) {
+					def = context.getResourceDefinition(resourceNameToSharedSupertype.get(resourceName));
+				} else {
+					def = context.getResourceDefinition(resourceName);
+				}
+				terser.addElement(resource, "type", def.getName());
+				terser.addElement(resource, "profile", def.getResourceProfile(serverBase));
+
+				for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
+					RestOperationTypeEnum resOpCode = nextMethodBinding.getRestOperationType();
+					if (resOpCode.isTypeLevel() || resOpCode.isInstanceLevel()) {
+						String resOp;
+						resOp = resOpCode.getCode();
+						if (resourceOps.contains(resOp) == false) {
+							resourceOps.add(resOp);
+							IBase interaction = terser.addElement(resource, "interaction");
+							terser.addElement(interaction, "code", resOp);
+						}
+						if (RestOperationTypeEnum.VREAD.equals(resOpCode)) {
+							// vread implies read
+							resOp = "read";
+							if (resourceOps.contains(resOp) == false) {
+								resourceOps.add(resOp);
+								IBase interaction = terser.addElement(resource, "interaction");
+								terser.addElement(interaction, "code", resOp);
+							}
+						}
+					}
+
+					if (nextMethodBinding.isSupportsConditional()) {
+						switch (resOpCode) {
+							case CREATE:
+								terser.setElement(resource, "conditionalCreate", "true");
+								break;
+							case DELETE:
+								if (nextMethodBinding.isSupportsConditionalMultiple()) {
+									terser.setElement(resource, "conditionalDelete", "multiple");
+								} else {
+									terser.setElement(resource, "conditionalDelete", "single");
+								}
+								break;
+							case UPDATE:
+								terser.setElement(resource, "conditionalUpdate", "true");
+								break;
+							case HISTORY_INSTANCE:
+							case HISTORY_SYSTEM:
+							case HISTORY_TYPE:
+							case READ:
+							case SEARCH_SYSTEM:
+							case SEARCH_TYPE:
+							case TRANSACTION:
+							case VALIDATE:
+							case VREAD:
+							case METADATA:
+							case META_ADD:
+							case META:
+							case META_DELETE:
+							case PATCH:
+							case BATCH:
+							case ADD_TAGS:
+							case DELETE_TAGS:
+							case GET_TAGS:
+							case GET_PAGE:
+							case GRAPHQL_REQUEST:
+							case EXTENDED_OPERATION_SERVER:
+							case EXTENDED_OPERATION_TYPE:
+							case EXTENDED_OPERATION_INSTANCE:
+							default:
+								break;
+						}
+					}
+
+					checkBindingForSystemOps(terser, rest, systemOps, nextMethodBinding);
+
+					// Resource Operations
+					if (nextMethodBinding instanceof SearchMethodBinding) {
+						addSearchMethodIfSearchIsNamedQuery(theRequestDetails, bindings, terser, operationNames, resource, (SearchMethodBinding) nextMethodBinding);
+					} else if (nextMethodBinding instanceof OperationMethodBinding) {
+						OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
+						String opName = bindings.getOperationBindingToId().get(methodBinding);
+						// Only add each operation (by name) once
+						if (operationNames.add(opName)) {
+							IBase operation = terser.addElement(resource, "operation");
+							populateOperation(theRequestDetails, terser, methodBinding, opName, operation);
+						}
+					}
+
+				}
+
+				// Find any global operations (Operations defines at the system level but with the
+				// global flag set to true, meaning they apply to all resource types)
+				if (globalMethodBindings != null) {
+					Set<String> globalOperationNames = new HashSet<>();
+					for (BaseMethodBinding<?> next : globalMethodBindings) {
+						if (next instanceof OperationMethodBinding) {
+							OperationMethodBinding methodBinding = (OperationMethodBinding) next;
+							if (methodBinding.isGlobalMethod()) {
+								if (methodBinding.isCanOperateAtInstanceLevel() || methodBinding.isCanOperateAtTypeLevel()) {
+									String opName = bindings.getOperationBindingToId().get(methodBinding);
+									// Only add each operation (by name) once
+									if (globalOperationNames.add(opName)) {
+										IBase operation = terser.addElement(resource, "operation");
+										populateOperation(theRequestDetails, terser, methodBinding, opName, operation);
+									}
+								}
+							}
+						}
+					}
+				}
+
+				ISearchParamRegistry serverConfiguration;
+				if (myServerConfiguration != null) {
+					serverConfiguration = myServerConfiguration;
+				} else {
+					serverConfiguration = myServer.createConfiguration();
+				}
+
+				/*
+				 * If we have an explicit registry (which will be the case in the JPA server) we use it as priority,
+				 * but also fill in any gaps using params from the server itself. This makes sure we include
+				 * global params like _lastUpdated
+				 */
+				Map<String, RuntimeSearchParam> searchParams;
+				ISearchParamRegistry searchParamRegistry;
+				if (mySearchParamRegistry != null) {
+					searchParamRegistry = mySearchParamRegistry;
+					searchParams = new HashMap<>(mySearchParamRegistry.getActiveSearchParams(resourceName));
+					for (Entry<String, RuntimeSearchParam> nextBuiltInSp : serverConfiguration.getActiveSearchParams(resourceName).entrySet()) {
+						if (nextBuiltInSp.getKey().startsWith("_") && !searchParams.containsKey(nextBuiltInSp.getKey())) {
+							searchParams.put(nextBuiltInSp.getKey(), nextBuiltInSp.getValue());
+						}
+					}
+				} else {
+					searchParamRegistry = serverConfiguration;
+					searchParams = serverConfiguration.getActiveSearchParams(resourceName);
+				}
+
+
+				for (RuntimeSearchParam next : searchParams.values()) {
+					IBase searchParam = terser.addElement(resource, "searchParam");
+					terser.addElement(searchParam, "name", next.getName());
+					terser.addElement(searchParam, "type", next.getParamType().getCode());
+					if (isNotBlank(next.getDescription())) {
+						terser.addElement(searchParam, "documentation", next.getDescription());
+					}
+
+					String spUri = next.getUri();
+					if (isBlank(spUri) && servletRequest != null) {
+						String id;
+						if (next.getId() != null) {
+							id = next.getId().toUnqualifiedVersionless().getValue();
+						} else {
+							id = resourceName + "-" + next.getName();
+						}
+						spUri = configuration.getServerAddressStrategy().determineServerBase(servletRequest.getServletContext(), servletRequest) + "/" + id;
+					}
+					if (isNotBlank(spUri)) {
+						terser.addElement(searchParam, "definition", spUri);
+					}
+				}
+
+				// Add Include to CapabilityStatement.rest.resource
+				NavigableSet<String> resourceIncludes = resourceNameToIncludes.get(resourceName);
+				if (resourceIncludes.isEmpty()) {
+					List<String> includes = searchParams
+						.values()
+						.stream()
+						.filter(t -> t.getParamType() == RestSearchParameterTypeEnum.REFERENCE)
+						.map(t -> resourceName + ":" + t.getName())
+						.sorted()
+						.collect(Collectors.toList());
+					terser.addElement(resource, "searchInclude", "*");
+					for (String nextInclude : includes) {
+						terser.addElement(resource, "searchInclude", nextInclude);
+					}
+				} else {
+					for (String resourceInclude : resourceIncludes) {
+						terser.addElement(resource, "searchInclude", resourceInclude);
+					}
+				}
+
+				// Add RevInclude to CapabilityStatement.rest.resource
+				if (myRestResourceRevIncludesEnabled) {
+					NavigableSet<String> resourceRevIncludes = resourceNameToRevIncludes.get(resourceName);
+					if (resourceRevIncludes.isEmpty()) {
+						TreeSet<String> revIncludes = new TreeSet<>();
+						for (String nextResourceName : resourceToMethods.keySet()) {
+							if (isBlank(nextResourceName)) {
+								continue;
+							}
+
+							for (RuntimeSearchParam t : searchParamRegistry.getActiveSearchParams(nextResourceName).values()) {
+								if (t.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
+									if (isNotBlank(t.getName())) {
+										boolean appropriateTarget = false;
+										if (t.getTargets().contains(resourceName) || t.getTargets().isEmpty()) {
+											appropriateTarget = true;
+										}
+
+										if (appropriateTarget) {
+											revIncludes.add(nextResourceName + ":" + t.getName());
+										}
+									}
+								}
+							}
+						}
+						for (String nextInclude : revIncludes) {
+							terser.addElement(resource, "searchRevInclude", nextInclude);
+						}
+					} else {
+						for (String resourceInclude : resourceRevIncludes) {
+							terser.addElement(resource, "searchRevInclude", resourceInclude);
+						}
+					}
+				}
+
+				// Add SupportedProfile to CapabilityStatement.rest.resource
+				for (String supportedProfile : resourceTypeToSupportedProfiles.get(resourceName)) {
+					terser.addElement(resource, "supportedProfile", supportedProfile);
+				}
+
+			} else {
+				for (BaseMethodBinding<?> nextMethodBinding : nextEntry.getValue()) {
+					checkBindingForSystemOps(terser, rest, systemOps, nextMethodBinding);
+					if (nextMethodBinding instanceof OperationMethodBinding) {
+						OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
+						if (!methodBinding.isGlobalMethod()) {
+							String opName = bindings.getOperationBindingToId().get(methodBinding);
+							if (operationNames.add(opName)) {
+								ourLog.debug("Found bound operation: {}", opName);
+								IBase operation = terser.addElement(rest, "operation");
+								populateOperation(theRequestDetails, terser, methodBinding, opName, operation);
+							}
+						}
+					} else if (nextMethodBinding instanceof SearchMethodBinding) {
+						addSearchMethodIfSearchIsNamedQuery(theRequestDetails, bindings, terser, operationNames, rest, (SearchMethodBinding) nextMethodBinding);
+					}
+				}
+			}
+
+		}
+
+
+		// Find any global operations (Operations defines at the system level but with the
+		// global flag set to true, meaning they apply to all resource types)
+		if (globalMethodBindings != null) {
+			Set<String> globalOperationNames = new HashSet<>();
+			for (BaseMethodBinding<?> next : globalMethodBindings) {
+				if (next instanceof OperationMethodBinding) {
+					OperationMethodBinding methodBinding = (OperationMethodBinding) next;
+					if (methodBinding.isGlobalMethod()) {
+						if (methodBinding.isCanOperateAtServerLevel()) {
+							String opName = bindings.getOperationBindingToId().get(methodBinding);
+							// Only add each operation (by name) once
+							if (globalOperationNames.add(opName)) {
+								IBase operation = terser.addElement(rest, "operation");
+								populateOperation(theRequestDetails, terser, methodBinding, opName, operation);
+							}
+						}
+					}
+				}
+			}
+		}
+
+
+		postProcessRest(terser, rest);
+		postProcess(terser, retVal);
+
+		return retVal;
+	}
+
+	private void addSearchMethodIfSearchIsNamedQuery(RequestDetails theRequestDetails, Bindings theBindings, FhirTerser theTerser, Set<String> theOperationNamesAlreadyAdded, IBase theElementToAddTo, SearchMethodBinding theSearchMethodBinding) {
+		if (theSearchMethodBinding.getQueryName() != null) {
+			String queryName = theBindings.getNamedSearchMethodBindingToName().get(theSearchMethodBinding);
+			if (theOperationNamesAlreadyAdded.add(queryName)) {
+				IBase operation = theTerser.addElement(theElementToAddTo, "operation");
+				theTerser.addElement(operation, "name", theSearchMethodBinding.getQueryName());
+				theTerser.addElement(operation, "definition", (createOperationUrl(theRequestDetails, queryName)));
+			}
+		}
+	}
+
+	private void populateOperation(RequestDetails theRequestDetails, FhirTerser theTerser, OperationMethodBinding theMethodBinding, String theOpName, IBase theOperation) {
+		String operationName = theMethodBinding.getName().substring(1);
+		theTerser.addElement(theOperation, "name", operationName);
+		theTerser.addElement(theOperation, "definition", createOperationUrl(theRequestDetails, theOpName));
+		if (isNotBlank(theMethodBinding.getDescription())) {
+			theTerser.addElement(theOperation, "documentation", theMethodBinding.getDescription());
+		}
+	}
+
+	@Nonnull
+	private String createOperationUrl(RequestDetails theRequestDetails, String theOpName) {
+		return getOperationDefinitionPrefix(theRequestDetails) + "OperationDefinition/" + theOpName;
+	}
+
+	private TreeMultimap<String, String> getSupportedProfileMultimap(FhirTerser terser) {
+		TreeMultimap<String, String> resourceTypeToSupportedProfiles = TreeMultimap.create();
+		if (myValidationSupport != null) {
+			List<IBaseResource> allStructureDefinitions = myValidationSupport.fetchAllNonBaseStructureDefinitions();
+			if (allStructureDefinitions != null) {
+				for (IBaseResource next : allStructureDefinitions) {
+					String kind = terser.getSinglePrimitiveValueOrNull(next, "kind");
+					String url = terser.getSinglePrimitiveValueOrNull(next, "url");
+					String baseDefinition = defaultString(terser.getSinglePrimitiveValueOrNull(next, "baseDefinition"));
+					if ("resource".equals(kind) && isNotBlank(url)) {
+
+						// Don't include the base resource definitions in the supported profile list - This isn't helpful
+						if (baseDefinition.equals("http://hl7.org/fhir/StructureDefinition/DomainResource") || baseDefinition.equals("http://hl7.org/fhir/StructureDefinition/Resource")) {
+							continue;
+						}
+
+						String resourceType = terser.getSinglePrimitiveValueOrNull(next, "snapshot.element.path");
+						if (isBlank(resourceType)) {
+							resourceType = terser.getSinglePrimitiveValueOrNull(next, "differential.element.path");
+						}
+
+						if (isNotBlank(resourceType)) {
+							resourceTypeToSupportedProfiles.put(resourceType, url);
+						}
+					}
+				}
+			}
+		}
+		return resourceTypeToSupportedProfiles;
+	}
+
+	/**
+	 * Subclasses may override
+	 */
+	protected void postProcess(FhirTerser theTerser, IBaseConformance theCapabilityStatement) {
+		// nothing
+	}
+
+	/**
+	 * Subclasses may override
+	 */
+	protected void postProcessRest(FhirTerser theTerser, IBase theRest) {
+		// nothing
+	}
+
+	/**
+	 * Subclasses may override
+	 */
+	protected void postProcessRestResource(FhirTerser theTerser, IBase theResource, String theResourceName) {
+		// nothing
+	}
+
+	protected String getOperationDefinitionPrefix(RequestDetails theRequestDetails) {
+		if (theRequestDetails == null) {
+			return "";
+		}
+		return theRequestDetails.getServerBaseForRequest() + "/";
+	}
+
+
+	@Override
+	@Read(typeName = "OperationDefinition")
+	public IBaseResource readOperationDefinition(@IdParam IIdType theId, RequestDetails theRequestDetails) {
+		if (theId == null || theId.hasIdPart() == false) {
+			throw new ResourceNotFoundException(theId);
+		}
+		RestfulServerConfiguration configuration = getServerConfiguration();
+		Bindings bindings = configuration.provideBindings();
+
+		List<OperationMethodBinding> operationBindings = bindings.getOperationIdToBindings().get(theId.getIdPart());
+		if (operationBindings != null && !operationBindings.isEmpty()) {
+			return readOperationDefinitionForOperation(theRequestDetails, bindings, operationBindings);
+		}
+
+		List<SearchMethodBinding> searchBindings = bindings.getSearchNameToBindings().get(theId.getIdPart());
+		if (searchBindings != null && !searchBindings.isEmpty()) {
+			return readOperationDefinitionForNamedSearch(searchBindings);
+		}
+		throw new ResourceNotFoundException(theId);
+	}
+
+	private IBaseResource readOperationDefinitionForNamedSearch(List<SearchMethodBinding> bindings) {
+		IBaseResource op = myContext.getResourceDefinition("OperationDefinition").newInstance();
+		FhirTerser terser = myContext.newTerser();
+
+		terser.addElement(op, "status", "active");
+		terser.addElement(op, "kind", "query");
+		terser.addElement(op, "affectsState", "false");
+
+		terser.addElement(op, "instance", "false");
+
+		Set<String> inParams = new HashSet<>();
+
+		String operationCode = null;
+		for (SearchMethodBinding binding : bindings) {
+			if (isNotBlank(binding.getDescription())) {
+				terser.addElement(op, "description", binding.getDescription());
+			}
+			if (isBlank(binding.getResourceProviderResourceName())) {
+				terser.addElement(op, "system", "true");
+				terser.addElement(op, "type", "false");
+			} else {
+				terser.addElement(op, "system", "false");
+				terser.addElement(op, "type", "true");
+				terser.addElement(op, "resource", binding.getResourceProviderResourceName());
+			}
+
+			if (operationCode == null) {
+				operationCode = binding.getQueryName();
+			}
+
+			for (IParameter nextParamUntyped : binding.getParameters()) {
+				if (nextParamUntyped instanceof SearchParameter) {
+					SearchParameter nextParam = (SearchParameter) nextParamUntyped;
+					if (!inParams.add(nextParam.getName())) {
+						continue;
+					}
+
+					IBase param = terser.addElement(op, "parameter");
+					terser.addElement(param, "use", "in");
+					terser.addElement(param, "type", "string");
+					terser.addElement(param, "searchType", nextParam.getParamType().getCode());
+					terser.addElement(param, "min", nextParam.isRequired() ? "1" : "0");
+					terser.addElement(param, "max", "1");
+					terser.addElement(param, "name", nextParam.getName());
+				}
+			}
+
+		}
+
+		terser.addElement(op, "code", operationCode);
+
+		String operationName = WordUtils.capitalize(operationCode);
+		terser.addElement(op, "name", operationName);
+
+		return op;
+	}
+
+	private IBaseResource readOperationDefinitionForOperation(RequestDetails theRequestDetails, Bindings theBindings, List<OperationMethodBinding> theOperationMethodBindings) {
+		IBaseResource op = myContext.getResourceDefinition("OperationDefinition").newInstance();
+		FhirTerser terser = myContext.newTerser();
+
+		terser.addElement(op, "status", "active");
+		terser.addElement(op, "kind", "operation");
+
+		boolean systemLevel = false;
+		boolean typeLevel = false;
+		boolean instanceLevel = false;
+		boolean affectsState = false;
+		String description = null;
+		String title = null;
+		String code = null;
+		String url = null;
+
+		Set<String> resourceNames = new TreeSet<>();
+		Map<String, IBase> inParams = new HashMap<>();
+		Map<String, IBase> outParams = new HashMap<>();
+
+		for (OperationMethodBinding operationMethodBinding : theOperationMethodBindings) {
+			if (isNotBlank(operationMethodBinding.getDescription()) && isBlank(description)) {
+				description = operationMethodBinding.getDescription();
+			}
+			if (isNotBlank(operationMethodBinding.getShortDescription()) && isBlank(title)) {
+				title = operationMethodBinding.getShortDescription();
+			}
+			if (operationMethodBinding.isCanOperateAtInstanceLevel()) {
+				instanceLevel = true;
+			}
+			if (operationMethodBinding.isCanOperateAtServerLevel()) {
+				systemLevel = true;
+			}
+			if (operationMethodBinding.isCanOperateAtTypeLevel()) {
+				typeLevel = true;
+			}
+			if (!operationMethodBinding.isIdempotent()) {
+				affectsState |= true;
+			}
+
+			code = operationMethodBinding.getName().substring(1);
+
+			if (isNotBlank(operationMethodBinding.getResourceName())) {
+				resourceNames.add(operationMethodBinding.getResourceName());
+			}
+
+			if (isBlank(url)) {
+				url = theBindings.getOperationBindingToId().get(operationMethodBinding);
+				if (isNotBlank(url)) {
+					url = createOperationUrl(theRequestDetails, url);
+				}
+			}
+
+
+			for (IParameter nextParamUntyped : operationMethodBinding.getParameters()) {
+				if (nextParamUntyped instanceof OperationParameter) {
+					OperationParameter nextParam = (OperationParameter) nextParamUntyped;
+
+					IBase param = inParams.get(nextParam.getName());
+					if (param == null){
+						param = terser.addElement(op, "parameter");
+						inParams.put(nextParam.getName(), param);
+					}
+
+					IBase existingParam = inParams.get(nextParam.getName());
+					if (isNotBlank(nextParam.getDescription()) && terser.getValues(existingParam, "documentation").isEmpty()) {
+						terser.addElement(existingParam, "documentation", nextParam.getDescription());
+					}
+
+					if (nextParam.getParamType() != null) {
+						String existingType = terser.getSinglePrimitiveValueOrNull(existingParam, "type");
+						if (!nextParam.getParamType().equals(existingType)) {
+							if (existingType == null) {
+								terser.setElement(existingParam, "type", nextParam.getParamType());
+							} else {
+								terser.setElement(existingParam, "type", "Resource");
+							}
+						}
+					}
+
+					terser.setElement(param, "use", "in");
+					if (nextParam.getSearchParamType() != null) {
+						terser.setElement(param, "searchType", nextParam.getSearchParamType());
+					}
+					terser.setElement(param, "min", Integer.toString(nextParam.getMin()));
+					terser.setElement(param, "max", (nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax())));
+					terser.setElement(param, "name", nextParam.getName());
+
+					List<IBaseExtension<?, ?>> existingExampleExtensions = ExtensionUtil.getExtensionsByUrl((IBaseHasExtensions) param, HapiExtensions.EXT_OP_PARAMETER_EXAMPLE_VALUE);
+					Set<String> existingExamples = existingExampleExtensions
+						.stream()
+						.map(t -> t.getValue())
+						.filter(t -> t != null)
+						.map(t -> (IPrimitiveType<?>) t)
+						.map(t -> t.getValueAsString())
+						.collect(Collectors.toSet());
+					for (String nextExample : nextParam.getExampleValues()) {
+						if (!existingExamples.contains(nextExample)) {
+							ExtensionUtil.addExtension(myContext, param, HapiExtensions.EXT_OP_PARAMETER_EXAMPLE_VALUE, "string", nextExample);
+						}
+					}
+
+				}
+			}
+
+			for (ReturnType nextParam : operationMethodBinding.getReturnParams()) {
+				if (outParams.containsKey(nextParam.getName())) {
+					continue;
+				}
+
+				IBase param = terser.addElement(op, "parameter");
+				outParams.put(nextParam.getName(), param);
+
+				terser.addElement(param, "use", "out");
+				if (nextParam.getType() != null) {
+					terser.addElement(param, "type", nextParam.getType());
+				}
+				terser.addElement(param, "min", Integer.toString(nextParam.getMin()));
+				terser.addElement(param, "max", (nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax())));
+				terser.addElement(param, "name", nextParam.getName());
+			}
+		}
+		String name = WordUtils.capitalize(code);
+
+		terser.addElements(op, "resource", resourceNames);
+		terser.addElement(op, "name", name);
+		terser.addElement(op, "url", url);
+		terser.addElement(op, "code", code);
+		terser.addElement(op, "description", description);
+		terser.addElement(op, "title", title);
+		terser.addElement(op, "affectsState", Boolean.toString(affectsState));
+		terser.addElement(op, "system", Boolean.toString(systemLevel));
+		terser.addElement(op, "type", Boolean.toString(typeLevel));
+		terser.addElement(op, "instance", Boolean.toString(instanceLevel));
+
+		return op;
+	}
+
+	@Override
+	public void setRestfulServer(RestfulServer theRestfulServer) {
+		// ignore
+	}
+
+	public void setRestResourceRevIncludesEnabled(boolean theRestResourceRevIncludesEnabled) {
+		myRestResourceRevIncludesEnabled = theRestResourceRevIncludesEnabled;
+	}
 }
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java
index fa5ac4e117c..8d90901b141 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java
@@ -49,11 +49,32 @@ public class ServletRequestDetails extends RequestDetails {
 	private HttpServletRequest myServletRequest;
 	private HttpServletResponse myServletResponse;
 
+	/**
+	 * Constructor for testing only
+	 */
+	public ServletRequestDetails() {
+		this((IInterceptorBroadcaster)null);
+	}
+
+	/**
+	 * Constructor
+	 */
 	public ServletRequestDetails(IInterceptorBroadcaster theInterceptorBroadcaster) {
 		super(theInterceptorBroadcaster);
 		setResponse(new ServletRestfulResponse(this));
 	}
 
+	/**
+	 * Copy constructor
+	 */
+	public ServletRequestDetails(ServletRequestDetails theRequestDetails) {
+		super(theRequestDetails);
+
+		myServer = theRequestDetails.getServer();
+		myServletRequest = theRequestDetails.getServletRequest();
+		myServletResponse = theRequestDetails.getServletResponse();
+	}
+
 	@Override
 	protected byte[] getByteStreamRequestContents() {
 		try {
diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/SearchMethodBindingTest.java b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/SearchMethodBindingTest.java
index c2f3add03e5..0a79048cb87 100644
--- a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/SearchMethodBindingTest.java
+++ b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/method/SearchMethodBindingTest.java
@@ -104,7 +104,7 @@ public class SearchMethodBindingTest {
 		when(requestDetails.getParameters()).thenReturn(params);
 
 		when(requestDetails.getUnqualifiedToQualifiedNames()).thenAnswer(t -> {
-			RequestDetails rd = new ServletRequestDetails(null);
+			RequestDetails rd = new ServletRequestDetails();
 			rd.setParameters(params);
 			return rd.getUnqualifiedToQualifiedNames();
 		});
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml
index 19eedc6bfd2..db762d268b7 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml
index 656d16080d7..d0ef59739a9 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir-spring-boot-samples</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml
index 0f9be244025..c7dba5afe65 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir-spring-boot-samples</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml
index 9d5b6483f92..fd0b2400bd2 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir-spring-boot-samples</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml
index a57c5e80e1b..23dc4f4cc96 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir-spring-boot</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>hapi-fhir-spring-boot-samples</artifactId>
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml
index 3f90ae6bbcc..37bb5fbff89 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
    <parent>
       <groupId>ca.uhn.hapi.fhir</groupId>
       <artifactId>hapi-deployable-pom</artifactId>
-      <version>5.4.0-PRE7-SNAPSHOT</version>
+      <version>5.4.0-PRE8-SNAPSHOT</version>
       <relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
    </parent>
 
diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml
index ff3fe4a179a..e3050e67351 100644
--- a/hapi-fhir-spring-boot/pom.xml
+++ b/hapi-fhir-spring-boot/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml
index 61d4ea265db..143a1fd9ffc 100644
--- a/hapi-fhir-structures-dstu2.1/pom.xml
+++ b/hapi-fhir-structures-dstu2.1/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/rest/server/ServerConformanceProvider.java b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/rest/server/ServerConformanceProvider.java
index 31045cdab6d..d9122d3f71e 100644
--- a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/rest/server/ServerConformanceProvider.java
+++ b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/rest/server/ServerConformanceProvider.java
@@ -275,7 +275,7 @@ public class ServerConformanceProvider extends BaseServerCapabilityStatementProv
 						handleSearchMethodBinding(rest, resource, resourceName, def, includes, (SearchMethodBinding) nextMethodBinding, theRequestDetails);
 					} else if (nextMethodBinding instanceof OperationMethodBinding) {
 						OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
-						String opName = bindings.getOperationBindingToName().get(methodBinding);
+						String opName = bindings.getOperationBindingToId().get(methodBinding);
 						if (operationNames.add(opName)) {
 							// Only add each operation (by name) once
 							rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition(new Reference("OperationDefinition/" + opName));
@@ -310,7 +310,7 @@ public class ServerConformanceProvider extends BaseServerCapabilityStatementProv
 					checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
 					if (nextMethodBinding instanceof OperationMethodBinding) {
 						OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
-						String opName = bindings.getOperationBindingToName().get(methodBinding);
+						String opName = bindings.getOperationBindingToId().get(methodBinding);
 						if (operationNames.add(opName)) {
 							ourLog.debug("Found bound operation: {}", opName);
 							rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition(new Reference("OperationDefinition/" + opName));
@@ -419,7 +419,7 @@ public class ServerConformanceProvider extends BaseServerCapabilityStatementProv
 		RestfulServerConfiguration serverConfiguration = getServerConfiguration(theRequestDetails);
 		Bindings bindings = serverConfiguration.provideBindings();
 
-		List<OperationMethodBinding> sharedDescriptions = bindings.getOperationNameToBindings().get(theId.getIdPart());
+		List<OperationMethodBinding> sharedDescriptions = bindings.getOperationIdToBindings().get(theId.getIdPart());
 		if (sharedDescriptions == null || sharedDescriptions.isEmpty()) {
 			throw new ResourceNotFoundException(theId);
 		}
diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu2_1Test.java
index f4bcddd6bbb..93970cb0ca7 100644
--- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu2_1Test.java
+++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu2_1Test.java
@@ -98,7 +98,7 @@ public class OperationServerDstu2_1Test {
 	 */
 	@Test
 	public void testOperationDefinition() {
-		OperationDefinition def = myFhirClient.read().resource(OperationDefinition.class).withId("OperationDefinition/Patient--OP_TYPE").execute();
+		OperationDefinition def = myFhirClient.read().resource(OperationDefinition.class).withId("OperationDefinition/Patient-t-OP_TYPE").execute();
 		
 		ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(def));
 		
diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2_1Test.java
index 8dbb9066dd8..63be245658c 100644
--- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2_1Test.java
+++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2_1Test.java
@@ -193,7 +193,7 @@ public class OperationServerWithSearchParamTypesDstu2_1Test {
 		/*
 		 * Check the operation definitions themselves
 		 */
-		OperationDefinition andListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient--andlist"), createRequestDetails(rs));
+		OperationDefinition andListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-t-andlist"), createRequestDetails(rs));
 		String def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(andListDef);
 		ourLog.info(def);
 		//@formatter:off
@@ -209,7 +209,7 @@ public class OperationServerWithSearchParamTypesDstu2_1Test {
 		));
 		//@formatter:on
 		
-		andListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient--andlist-withnomax"), createRequestDetails(rs));
+		andListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-t-andlist-withnomax"), createRequestDetails(rs));
 		def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(andListDef);
 		ourLog.info(def);
 		//@formatter:off
@@ -225,7 +225,7 @@ public class OperationServerWithSearchParamTypesDstu2_1Test {
 		));
 		//@formatter:on
 
-		OperationDefinition orListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient--orlist"), createRequestDetails(rs));
+		OperationDefinition orListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-t-orlist"), createRequestDetails(rs));
 		def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(orListDef);
 		ourLog.info(def);
 		//@formatter:off
@@ -241,7 +241,7 @@ public class OperationServerWithSearchParamTypesDstu2_1Test {
 		));
 		//@formatter:on
 		
-		orListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient--orlist-withnomax"), createRequestDetails(rs));
+		orListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-t-orlist-withnomax"), createRequestDetails(rs));
 		def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(orListDef);
 		ourLog.info(def);
 		//@formatter:off
@@ -505,7 +505,7 @@ public class OperationServerWithSearchParamTypesDstu2_1Test {
 
 
 	private RequestDetails createRequestDetails(RestfulServer theServer) {
-		ServletRequestDetails retVal = new ServletRequestDetails(null);
+		ServletRequestDetails retVal = new ServletRequestDetails();
 		retVal.setServer(theServer);
 		return retVal;
 	}
diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml
index 9359dc2e980..eba9cfc2801 100644
--- a/hapi-fhir-structures-dstu2/pom.xml
+++ b/hapi-fhir-structures-dstu2/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java
index cf51eda513a..79d0b6fc093 100644
--- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java
+++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java
@@ -271,7 +271,7 @@ public class ServerConformanceProvider extends BaseServerCapabilityStatementProv
 						handleSearchMethodBinding(resource, def, includes, (SearchMethodBinding) nextMethodBinding, theRequestDetails);
 					} else if (nextMethodBinding instanceof OperationMethodBinding) {
 						OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
-						String opName = bindings.getOperationBindingToName().get(methodBinding);
+						String opName = bindings.getOperationBindingToId().get(methodBinding);
 						if (operationNames.add(opName)) {
 							// Only add each operation (by name) once
 							rest.addOperation().setName(methodBinding.getName().substring(1)).getDefinition().setReference("OperationDefinition/" + opName);
@@ -306,7 +306,7 @@ public class ServerConformanceProvider extends BaseServerCapabilityStatementProv
 					checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
 					if (nextMethodBinding instanceof OperationMethodBinding) {
 						OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
-						String opName = bindings.getOperationBindingToName().get(methodBinding);
+						String opName = bindings.getOperationBindingToId().get(methodBinding);
 						if (operationNames.add(opName)) {
 							rest.addOperation().setName(methodBinding.getName().substring(1)).getDefinition().setReference("OperationDefinition/" + opName);
 						}
@@ -415,7 +415,7 @@ public class ServerConformanceProvider extends BaseServerCapabilityStatementProv
 		RestfulServerConfiguration serverConfiguration = getServerConfiguration(theRequestDetails);
 		Bindings bindings = serverConfiguration.provideBindings();
 
-		List<OperationMethodBinding> sharedDescriptions = bindings.getOperationNameToBindings().get(theId.getIdPart());
+		List<OperationMethodBinding> sharedDescriptions = bindings.getOperationIdToBindings().get(theId.getIdPart());
 		if (sharedDescriptions == null || sharedDescriptions.isEmpty()) {
 			throw new ResourceNotFoundException(theId);
 		}
@@ -449,10 +449,10 @@ public class ServerConformanceProvider extends BaseServerCapabilityStatementProv
 			for (IParameter nextParamUntyped : sharedDescription.getParameters()) {
 				if (nextParamUntyped instanceof OperationParameter) {
 					OperationParameter nextParam = (OperationParameter) nextParamUntyped;
-					Parameter param = op.addParameter();
 					if (!inParams.add(nextParam.getName())) {
 						continue;
 					}
+					Parameter param = op.addParameter();
 					param.setUse(OperationParameterUseEnum.IN);
 					if (nextParam.getParamType() != null) {
 						param.setType(nextParam.getParamType());
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java
index d2681d3376b..40ada003db2 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java
@@ -1134,7 +1134,7 @@ public class XmlParserDstu2Test {
 		ourLog.info(string);
 
 		parsed = parser.parseResource(Composition.class, string);
-		assertEquals(2, parsed.getContained().getContainedResources().size());
+		assertEquals(3, parsed.getContained().getContainedResources().size());
 	}
 
 	/**
@@ -1159,7 +1159,7 @@ public class XmlParserDstu2Test {
 		ourLog.info(string);
 
 		parsed = parser.parseResource(Composition.class, string);
-		assertEquals(2, parsed.getContained().getContainedResources().size());
+		assertEquals(3, parsed.getContained().getContainedResources().size());
 	}
 
 	@Test
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationDuplicateServerDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationDuplicateServerDstu2Test.java
index b042b34659b..db916678122 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationDuplicateServerDstu2Test.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationDuplicateServerDstu2Test.java
@@ -50,14 +50,14 @@ public class OperationDuplicateServerDstu2Test {
 			ourLog.info(response);
 
 			Conformance resp = ourCtx.newXmlParser().parseResource(Conformance.class, response);
-			assertEquals(3, resp.getRest().get(0).getOperation().size());
+			assertEquals(1, resp.getRest().get(0).getOperation().size());
 			assertEquals("myoperation", resp.getRest().get(0).getOperation().get(0).getName());
-			assertEquals("OperationDefinition/-s-myoperation", resp.getRest().get(0).getOperation().get(0).getDefinition().getReference().getValue());
+			assertEquals("OperationDefinition/OrganizationPatient-ts-myoperation", resp.getRest().get(0).getOperation().get(0).getDefinition().getReference().getValue());
 		}
 
 		// OperationDefinition
 		{
-			HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/OperationDefinition/-s-myoperation?_pretty=true");
+			HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/OperationDefinition/OrganizationPatient-ts-myoperation?_pretty=true");
 			HttpResponse status = ourClient.execute(httpGet);
 
 			assertEquals(200, status.getStatusLine().getStatusCode());
@@ -69,25 +69,7 @@ public class OperationDuplicateServerDstu2Test {
 			assertEquals(true, resp.getSystemElement().getValue().booleanValue());
 			assertEquals("myoperation", resp.getCode());
 			assertEquals(true, resp.getIdempotent().booleanValue());
-			assertEquals(0, resp.getType().size());
-			assertEquals(1, resp.getParameter().size());
-		}
-		// OperationDefinition
-		{
-			HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/OperationDefinition/Organization--myoperation?_pretty=true");
-			HttpResponse status = ourClient.execute(httpGet);
-
-			assertEquals(200, status.getStatusLine().getStatusCode());
-			String response = IOUtils.toString(status.getEntity().getContent());
-			IOUtils.closeQuietly(status.getEntity().getContent());
-			ourLog.info(response);
-
-			OperationDefinition resp = ourCtx.newXmlParser().parseResource(OperationDefinition.class, response);
-			assertEquals(false, resp.getSystemElement().getValue().booleanValue());
-			assertEquals("myoperation", resp.getCode());
-			assertEquals(true, resp.getIdempotent().booleanValue());
-			assertEquals(1, resp.getType().size());
-			assertEquals("Organization", resp.getType().get(0).getValue());
+			assertEquals(2, resp.getType().size());
 			assertEquals(1, resp.getParameter().size());
 		}
 	}
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu2Test.java
index b6f51ed2243..4dfa2813cc4 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu2Test.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu2Test.java
@@ -105,7 +105,7 @@ public class OperationServerDstu2Test {
 		List<String> opNames = toOpNames(ops);
 		assertThat(opNames, containsInRelativeOrder("OP_TYPE"));
 		
-		assertEquals("OperationDefinition/Patient--OP_TYPE", ops.get(opNames.indexOf("OP_TYPE")).getDefinition().getReference().getValue());
+		assertEquals("OperationDefinition/Patient-t-OP_TYPE", ops.get(opNames.indexOf("OP_TYPE")).getDefinition().getReference().getValue());
 	}
 
 	/**
@@ -113,7 +113,7 @@ public class OperationServerDstu2Test {
 	 */
 	@Test
 	public void testOperationDefinition() {
-		OperationDefinition def = myFhirClient.read().resource(OperationDefinition.class).withId("OperationDefinition/Patient--OP_TYPE").execute();
+		OperationDefinition def = myFhirClient.read().resource(OperationDefinition.class).withId("OperationDefinition/Patient-t-OP_TYPE").execute();
 		
 		ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(def));
 		
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2Test.java
index 837db498fd1..5b7ff389697 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2Test.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu2Test.java
@@ -182,7 +182,7 @@ public class OperationServerWithSearchParamTypesDstu2Test {
 		/*
 		 * Check the operation definitions themselves
 		 */
-		OperationDefinition andListDef = sc.readOperationDefinition(new IdDt("OperationDefinition/Patient--andlist"), createRequestDetails(rs));
+		OperationDefinition andListDef = sc.readOperationDefinition(new IdDt("OperationDefinition/Patient-t-andlist"), createRequestDetails(rs));
 		String def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(andListDef);
 		ourLog.info(def);
 		//@formatter:off
@@ -418,7 +418,7 @@ public class OperationServerWithSearchParamTypesDstu2Test {
 	}
 
 	private RequestDetails createRequestDetails(RestfulServer theServer) {
-		ServletRequestDetails retVal = new ServletRequestDetails(null);
+		ServletRequestDetails retVal = new ServletRequestDetails();
 		retVal.setServer(theServer);
 		return retVal;
 	}
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderDstu2Test.java
index a0be908d5ce..1d6bf3f7930 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderDstu2Test.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderDstu2Test.java
@@ -25,7 +25,21 @@ import ca.uhn.fhir.model.primitive.DateDt;
 import ca.uhn.fhir.model.primitive.IdDt;
 import ca.uhn.fhir.model.primitive.StringDt;
 import ca.uhn.fhir.model.primitive.UriDt;
-import ca.uhn.fhir.rest.annotation.*;
+import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
+import ca.uhn.fhir.rest.annotation.Create;
+import ca.uhn.fhir.rest.annotation.Delete;
+import ca.uhn.fhir.rest.annotation.History;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.IncludeParam;
+import ca.uhn.fhir.rest.annotation.Operation;
+import ca.uhn.fhir.rest.annotation.OperationParam;
+import ca.uhn.fhir.rest.annotation.OptionalParam;
+import ca.uhn.fhir.rest.annotation.Read;
+import ca.uhn.fhir.rest.annotation.RequiredParam;
+import ca.uhn.fhir.rest.annotation.ResourceParam;
+import ca.uhn.fhir.rest.annotation.Search;
+import ca.uhn.fhir.rest.annotation.Update;
+import ca.uhn.fhir.rest.annotation.Validate;
 import ca.uhn.fhir.rest.api.MethodOutcome;
 import ca.uhn.fhir.rest.api.server.IBundleProvider;
 import ca.uhn.fhir.rest.api.server.RequestDetails;
@@ -67,9 +81,9 @@ import static org.mockito.Mockito.when;
 
 public class ServerConformanceProviderDstu2Test {
 
+	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerConformanceProviderDstu2Test.class);
 	private static FhirContext ourCtx;
 	private static FhirValidator ourValidator;
-	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerConformanceProviderDstu2Test.class);
 
 	static {
 		ourCtx = FhirContext.forDstu2();
@@ -85,7 +99,7 @@ public class ServerConformanceProviderDstu2Test {
 		ValidationResult result = ourValidator.validateWithResult(theOpDef);
 		String outcome = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
 		ourLog.info("Outcome: {}", outcome);
-		
+
 		assertTrue(result.isSuccessful(), outcome);
 	}
 
@@ -97,6 +111,7 @@ public class ServerConformanceProviderDstu2Test {
 		when(req.getContextPath()).thenReturn("/FhirStorm");
 		return req;
 	}
+
 	private ServletConfig createServletConfig() {
 		ServletConfig sc = mock(ServletConfig.class);
 		when(sc.getServletContext()).thenReturn(null);
@@ -255,7 +270,9 @@ public class ServerConformanceProviderDstu2Test {
 		assertNull(res.getConditionalUpdate());
 	}
 
-	/** See #379 */
+	/**
+	 * See #379
+	 */
 	@Test
 	public void testOperationAcrossMultipleTypes() throws Exception {
 		RestfulServer rs = new RestfulServer(ourCtx);
@@ -271,55 +288,26 @@ public class ServerConformanceProviderDstu2Test {
 		String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
 		ourLog.info(conf);
 
-		assertEquals(4, conformance.getRest().get(0).getOperation().size());
+		assertEquals(2, conformance.getRest().get(0).getOperation().size());
 		List<String> operationNames = toOperationNames(conformance.getRest().get(0).getOperation());
-		assertThat(operationNames, containsInAnyOrder("someOp", "validate", "someOp", "validate"));
-		
+		assertThat(operationNames, containsInAnyOrder("someOp", "validate"));
+
 		List<String> operationIdParts = toOperationIdParts(conformance.getRest().get(0).getOperation());
-		assertThat(operationIdParts, containsInAnyOrder("Patient-i-someOp","Encounter-i-someOp","Patient-i-validate","Encounter-i-validate"));
-		
+		assertThat(operationIdParts, containsInAnyOrder("EncounterPatient-i-someOp", "EncounterPatient-i-validate"));
+
 		{
-			OperationDefinition opDef = sc.readOperationDefinition(new IdDt("OperationDefinition/Patient-i-someOp"), createRequestDetails(rs));
-			validate(opDef);
-			
-			Set<String> types = toStrings(opDef.getType());
-			assertEquals("someOp", opDef.getCode());
-			assertEquals(true, opDef.getInstance());
-			assertEquals(false, opDef.getSystem());
-			assertThat(types, containsInAnyOrder("Patient"));
-			assertEquals(2, opDef.getParameter().size());
-			assertEquals("someOpParam1", opDef.getParameter().get(0).getName());
-			assertEquals("date", opDef.getParameter().get(0).getType());
-			assertEquals("someOpParam2", opDef.getParameter().get(1).getName());
-			assertEquals("Patient", opDef.getParameter().get(1).getType());
-		}
-		{
-			OperationDefinition opDef = sc.readOperationDefinition(new IdDt("OperationDefinition/Encounter-i-someOp"), createRequestDetails(rs));
+			OperationDefinition opDef = sc.readOperationDefinition(new IdDt("OperationDefinition/EncounterPatient-i-someOp"), createRequestDetails(rs));
 			validate(opDef);
 
 			Set<String> types = toStrings(opDef.getType());
 			assertEquals("someOp", opDef.getCode());
 			assertEquals(true, opDef.getInstance());
 			assertEquals(false, opDef.getSystem());
-			assertThat(types, containsInAnyOrder("Encounter"));
+			assertThat(types, containsInAnyOrder("Patient", "Encounter"));
 			assertEquals(2, opDef.getParameter().size());
 			assertEquals("someOpParam1", opDef.getParameter().get(0).getName());
 			assertEquals("date", opDef.getParameter().get(0).getType());
 			assertEquals("someOpParam2", opDef.getParameter().get(1).getName());
-			assertEquals("Encounter", opDef.getParameter().get(1).getType());
-		}
-		{
-			OperationDefinition opDef = sc.readOperationDefinition(new IdDt("OperationDefinition/Patient-i-validate"), createRequestDetails(rs));
-			validate(opDef);
-
-			Set<String> types = toStrings(opDef.getType());
-			assertEquals("validate", opDef.getCode());
-			assertEquals(true, opDef.getInstance());
-			assertEquals(false, opDef.getSystem());
-			assertThat(types, containsInAnyOrder("Patient"));
-			assertEquals(1, opDef.getParameter().size());
-			assertEquals("resource", opDef.getParameter().get(0).getName());
-			assertEquals("Patient", opDef.getParameter().get(0).getType());
 		}
 	}
 
@@ -344,45 +332,9 @@ public class ServerConformanceProviderDstu2Test {
 
 	}
 
-	@Test
-	public void testOperationOnNoTypes() throws Exception {
-		RestfulServer rs = new RestfulServer(ourCtx);
-		rs.setProviders(new PlainProviderWithExtendedOperationOnNoType());
-
-		ServerConformanceProvider sc = new ServerConformanceProvider(rs) {
-			@Override
-			public Conformance getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
-				return super.getServerConformance(theRequest, theRequestDetails);
-			}
-		};
-		rs.setServerConformanceProvider(sc);
-
-		rs.init(createServletConfig());
-
-		Conformance sconf = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-		assertEquals("OperationDefinition/-is-plain", sconf.getRest().get(0).getOperation().get(0).getDefinition().getReference().getValue());
-
-		OperationDefinition opDef = sc.readOperationDefinition(new IdDt("OperationDefinition/-is-plain"), createRequestDetails(rs));
-		validate(opDef);
-
-		assertEquals("plain", opDef.getCode());
-		assertEquals(true, opDef.getIdempotent().booleanValue());
-		assertEquals(3, opDef.getParameter().size());
-		assertEquals("start", opDef.getParameter().get(0).getName());
-		assertEquals("in", opDef.getParameter().get(0).getUse());
-		assertEquals("0", opDef.getParameter().get(0).getMinElement().getValueAsString());
-		assertEquals("date", opDef.getParameter().get(0).getTypeElement().getValueAsString());
-
-		assertEquals("out1", opDef.getParameter().get(2).getName());
-		assertEquals("out", opDef.getParameter().get(2).getUse());
-		assertEquals("1", opDef.getParameter().get(2).getMinElement().getValueAsString());
-		assertEquals("2", opDef.getParameter().get(2).getMaxElement().getValueAsString());
-		assertEquals("string", opDef.getParameter().get(2).getTypeElement().getValueAsString());
-	}
-
 	@Test
 	public void testProviderForSmart() throws ServletException {
-		
+
 		RestfulServer rs = new RestfulServer(ourCtx);
 		rs.createConfiguration();
 		rs.setProviders(new ProviderWithRequiredAndOptional());
@@ -391,28 +343,28 @@ public class ServerConformanceProviderDstu2Test {
 			@Override
 			public Conformance getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
 				Conformance conformance = super.getServerConformance(theRequest, theRequestDetails);
-				 ExtensionDt extensionDt = new ExtensionDt();
-				 ExtensionDt extensionDtToken = new ExtensionDt();
-				 ExtensionDt extensionDtAuthorize = new ExtensionDt();
-				 Rest rest = conformance.getRestFirstRep();
-				 RestSecurity restSecurity = rest.getSecurity();
-				 
-				 conformance.setAcceptUnknown(UnknownContentCodeEnum.UNKNOWN_ELEMENTS_AND_EXTENSIONS);  
-				 restSecurity.addService(RestfulSecurityServiceEnum.SMART_ON_FHIR);
-				 restSecurity.getServiceFirstRep().setText("OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org)");	 
-				 extensionDt.setUrl("http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris");
-				 extensionDtToken.setUrl("token");
-				 extensionDtToken.setValue(new UriDt("https://SERVERNAME/token"));
-				 extensionDtAuthorize.setUrl("authorize");
-				 extensionDtAuthorize.setValue(new UriDt("https://SERVERNAME/authorize"));
-				 extensionDt.addUndeclaredExtension(extensionDtToken);
+				ExtensionDt extensionDt = new ExtensionDt();
+				ExtensionDt extensionDtToken = new ExtensionDt();
+				ExtensionDt extensionDtAuthorize = new ExtensionDt();
+				Rest rest = conformance.getRestFirstRep();
+				RestSecurity restSecurity = rest.getSecurity();
+
+				conformance.setAcceptUnknown(UnknownContentCodeEnum.UNKNOWN_ELEMENTS_AND_EXTENSIONS);
+				restSecurity.addService(RestfulSecurityServiceEnum.SMART_ON_FHIR);
+				restSecurity.getServiceFirstRep().setText("OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org)");
+				extensionDt.setUrl("http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris");
+				extensionDtToken.setUrl("token");
+				extensionDtToken.setValue(new UriDt("https://SERVERNAME/token"));
+				extensionDtAuthorize.setUrl("authorize");
+				extensionDtAuthorize.setValue(new UriDt("https://SERVERNAME/authorize"));
+				extensionDt.addUndeclaredExtension(extensionDtToken);
 				extensionDt.addUndeclaredExtension(extensionDtAuthorize);
 				restSecurity.addUndeclaredExtension(extensionDt);
-				
+
 				return conformance;
 			}
 		};
-	
+
 		rs.init(createServletConfig());
 
 		Conformance conformance = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
@@ -561,8 +513,8 @@ public class ServerConformanceProviderDstu2Test {
 		ourLog.info(conf);
 
 	}
-	
-	
+
+
 	/**
 	 * See #286
 	 */
@@ -595,7 +547,7 @@ public class ServerConformanceProviderDstu2Test {
 		ourLog.info(conf);
 
 		RestResource resource = findRestResource(conformance, "Patient");
-		
+
 		RestResourceSearchParam param = resource.getSearchParam().get(0);
 		assertEquals("bar", param.getChain().get(0).getValue());
 		assertEquals("foo", param.getChain().get(1).getValue());
@@ -696,7 +648,7 @@ public class ServerConformanceProviderDstu2Test {
 		ValidationResult result = ourCtx.newValidator().validateWithResult(conformance);
 		assertTrue(result.isSuccessful(), result.getMessages().toString());
 	}
-	
+
 	private List<String> toOperationIdParts(List<RestOperation> theOperation) {
 		ArrayList<String> retVal = Lists.newArrayList();
 		for (RestOperation next : theOperation) {
@@ -721,9 +673,10 @@ public class ServerConformanceProviderDstu2Test {
 		return retVal;
 	}
 
-	@AfterAll
-	public static void afterClassClearContext() {
-		TestUtil.clearAllStaticFieldsForUnitTest();
+	private RequestDetails createRequestDetails(RestfulServer theServer) {
+		ServletRequestDetails retVal = new ServletRequestDetails();
+		retVal.setServer(theServer);
+		return retVal;
 	}
 
 	public static class ConditionalProvider implements IResourceProvider {
@@ -779,7 +732,7 @@ public class ServerConformanceProviderDstu2Test {
 
 		@Operation(name = "someOp")
 		public IBundleProvider everything(javax.servlet.http.HttpServletRequest theServletRequest, @IdParam IdDt theId,
-				@OperationParam(name = "someOpParam1") DateDt theStart, @OperationParam(name = "someOpParam2") Encounter theEnd) {
+													 @OperationParam(name = "someOpParam1") DateDt theStart, @OperationParam(name = "someOpParam2") Encounter theEnd) {
 			return null;
 		}
 
@@ -799,7 +752,7 @@ public class ServerConformanceProviderDstu2Test {
 
 		@Operation(name = "someOp")
 		public IBundleProvider everything(javax.servlet.http.HttpServletRequest theServletRequest, @IdParam IdDt theId,
-				@OperationParam(name = "someOpParam1") DateDt theStart, @OperationParam(name = "someOpParam2") Patient theEnd) {
+													 @OperationParam(name = "someOpParam1") DateDt theStart, @OperationParam(name = "someOpParam2") Patient theEnd) {
 			return null;
 		}
 
@@ -841,7 +794,7 @@ public class ServerConformanceProviderDstu2Test {
 
 	public static class PlainProviderWithExtendedOperationOnNoType {
 
-		@Operation(name = "plain", idempotent = true, returnParameters = { @OperationParam(min = 1, max = 2, name = "out1", type = StringDt.class) })
+		@Operation(name = "plain", idempotent = true, returnParameters = {@OperationParam(min = 1, max = 2, name = "out1", type = StringDt.class)})
 		public IBundleProvider everything(javax.servlet.http.HttpServletRequest theServletRequest, @IdParam ca.uhn.fhir.model.primitive.IdDt theId, @OperationParam(name = "start") DateDt theStart, @OperationParam(name = "end") DateDt theEnd) {
 			return null;
 		}
@@ -867,7 +820,7 @@ public class ServerConformanceProviderDstu2Test {
 		@Description(shortDefinition = "This is a search for stuff!")
 		@Search
 		public List<DiagnosticReport> findDiagnosticReportsByPatient(@RequiredParam(name = DiagnosticReport.SP_SUBJECT + '.' + Patient.SP_IDENTIFIER) IdentifierDt thePatientId, @OptionalParam(name = DiagnosticReport.SP_CODE) TokenOrListParam theNames,
-				@OptionalParam(name = DiagnosticReport.SP_DATE) DateRangeParam theDateRange, @IncludeParam(allow = { "DiagnosticReport.result" }) Set<Include> theIncludes) throws Exception {
+																						 @OptionalParam(name = DiagnosticReport.SP_DATE) DateRangeParam theDateRange, @IncludeParam(allow = {"DiagnosticReport.result"}) Set<Include> theIncludes) throws Exception {
 			return null;
 		}
 
@@ -895,7 +848,7 @@ public class ServerConformanceProviderDstu2Test {
 		}
 
 		@Search(type = Patient.class)
-		public Patient findPatient2(@Description(shortDefinition = "All patients linked to the given patient") @OptionalParam(name = "link", targetTypes = { Patient.class }) ReferenceAndListParam theLink) {
+		public Patient findPatient2(@Description(shortDefinition = "All patients linked to the given patient") @OptionalParam(name = "link", targetTypes = {Patient.class}) ReferenceAndListParam theLink) {
 			return null;
 		}
 
@@ -905,8 +858,8 @@ public class ServerConformanceProviderDstu2Test {
 
 		@Search(type = Patient.class)
 		public Patient findPatient1(
-				@Description(shortDefinition = "The organization at which this person is a patient") 
-				@RequiredParam(name = Patient.SP_ORGANIZATION, chainWhitelist= {"foo", "bar"}) 
+			@Description(shortDefinition = "The organization at which this person is a patient")
+			@RequiredParam(name = Patient.SP_ORGANIZATION, chainWhitelist = {"foo", "bar"})
 				ReferenceAndListParam theIdentifier) {
 			return null;
 		}
@@ -918,8 +871,8 @@ public class ServerConformanceProviderDstu2Test {
 		@Search(type = Patient.class)
 		public Patient findPatient1(
 			@Description(shortDefinition = "The organization at which this person is a patient")
-			@RequiredParam(name = "organization.foo")	ReferenceAndListParam theFoo,
-			@RequiredParam(name = "organization.bar")	ReferenceAndListParam theBar,
+			@RequiredParam(name = "organization.foo") ReferenceAndListParam theFoo,
+			@RequiredParam(name = "organization.bar") ReferenceAndListParam theBar,
 			@RequiredParam(name = "organization.baz.bob") ReferenceAndListParam theBazbob) {
 			return null;
 		}
@@ -966,10 +919,9 @@ public class ServerConformanceProviderDstu2Test {
 
 	}
 
-	private RequestDetails createRequestDetails(RestfulServer theServer) {
-		ServletRequestDetails retVal = new ServletRequestDetails(null);
-		retVal.setServer(theServer);
-		return retVal;
+	@AfterAll
+	public static void afterClassClearContext() {
+		TestUtil.clearAllStaticFieldsForUnitTest();
 	}
 
 
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorUserDataMapDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorUserDataMapDstu2Test.java
index 6f46fa68e66..d543ecfa2d1 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorUserDataMapDstu2Test.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/InterceptorUserDataMapDstu2Test.java
@@ -42,6 +42,7 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -78,7 +79,7 @@ public class InterceptorUserDataMapDstu2Test {
 	@BeforeEach
 	public void beforePurgeMap() {
 		myMap = null;
-		myMapCheckMethods = new LinkedHashSet<>();
+		myMapCheckMethods = Collections.synchronizedSet(new LinkedHashSet<>());
 	}
 
 
diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml
index 5a825a62517..f9d795a9068 100644
--- a/hapi-fhir-structures-dstu3/pom.xml
+++ b/hapi-fhir-structures-dstu3/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerCapabilityStatementProvider.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerCapabilityStatementProvider.java
index c01caf7a9db..1deaa202fc2 100644
--- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerCapabilityStatementProvider.java
+++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerCapabilityStatementProvider.java
@@ -318,7 +318,7 @@ public class ServerCapabilityStatementProvider extends BaseServerCapabilityState
             }
           } else if (nextMethodBinding instanceof OperationMethodBinding) {
             OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
-            String opName = bindings.getOperationBindingToName().get(methodBinding);
+            String opName = bindings.getOperationBindingToId().get(methodBinding);
             if (operationNames.add(opName)) {
               // Only add each operation (by name) once
               rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition(new Reference("OperationDefinition/" + opName));
@@ -353,7 +353,7 @@ public class ServerCapabilityStatementProvider extends BaseServerCapabilityState
           checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
           if (nextMethodBinding instanceof OperationMethodBinding) {
             OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
-            String opName = bindings.getOperationBindingToName().get(methodBinding);
+            String opName = bindings.getOperationBindingToId().get(methodBinding);
             if (operationNames.add(opName)) {
               ourLog.debug("Found bound operation: {}", opName);
               rest.addOperation().setName(methodBinding.getName().substring(1)).setDefinition(new Reference("OperationDefinition/" + opName));
@@ -466,7 +466,7 @@ public class ServerCapabilityStatementProvider extends BaseServerCapabilityState
     RestfulServerConfiguration serverConfiguration = getServerConfiguration(theRequestDetails);
     Bindings bindings = serverConfiguration.provideBindings();
 
-    List<OperationMethodBinding> operationBindings = bindings.getOperationNameToBindings().get(theId.getIdPart());
+    List<OperationMethodBinding> operationBindings = bindings.getOperationIdToBindings().get(theId.getIdPart());
     if (operationBindings != null && !operationBindings.isEmpty()) {
         return readOperationDefinitionForOperation(operationBindings);
     }
@@ -572,10 +572,10 @@ public class ServerCapabilityStatementProvider extends BaseServerCapabilityState
       for (IParameter nextParamUntyped : sharedDescription.getParameters()) {
         if (nextParamUntyped instanceof OperationParameter) {
           OperationParameter nextParam = (OperationParameter) nextParamUntyped;
-          OperationDefinitionParameterComponent param = op.addParameter();
           if (!inParams.add(nextParam.getName())) {
             continue;
           }
+          OperationDefinitionParameterComponent param = op.addParameter();
           param.setUse(OperationParameterUse.IN);
           if (nextParam.getParamType() != null) {
             param.setType(nextParam.getParamType());
diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu3Test.java
index 63c2f2befd8..b5382da1516 100644
--- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu3Test.java
+++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu3Test.java
@@ -102,7 +102,7 @@ public class OperationServerDstu3Test {
 	 */
 	@Test
 	public void testOperationDefinition() {
-		OperationDefinition def = myFhirClient.read().resource(OperationDefinition.class).withId("OperationDefinition/Patient--OP_TYPE").execute();
+		OperationDefinition def = myFhirClient.read().resource(OperationDefinition.class).withId("OperationDefinition/Patient-t-OP_TYPE").execute();
 		
 		ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(def));
 		
@@ -565,14 +565,6 @@ public class OperationServerDstu3Test {
 	}
 
 	
-	public static void main(String[] theValue) {
-		Parameters p = new Parameters();
-		p.addParameter().setName("start").setValue(new DateTimeType("2001-01-02"));
-		p.addParameter().setName("end").setValue(new DateTimeType("2015-07-10"));
-		String inParamsStr = FhirContext.forDstu2().newXmlParser().encodeResourceToString(p);
-		ourLog.info(inParamsStr.replace("\"", "\\\""));
-	}
-
 	public static class PatientProvider implements IResourceProvider {
 
 		@Override
diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu3Test.java
index 6393450147a..3842f6ba77c 100644
--- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu3Test.java
+++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/OperationServerWithSearchParamTypesDstu3Test.java
@@ -193,7 +193,7 @@ public class OperationServerWithSearchParamTypesDstu3Test {
 		/*
 		 * Check the operation definitions themselves
 		 */
-		OperationDefinition andListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient--andlist"), createRequestDetails(rs));
+		OperationDefinition andListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-t-andlist"), createRequestDetails(rs));
 		String def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(andListDef);
 		ourLog.info(def);
 		//@formatter:off
@@ -209,7 +209,7 @@ public class OperationServerWithSearchParamTypesDstu3Test {
 		));
 		//@formatter:on
 		
-		andListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient--andlist-withnomax"), createRequestDetails(rs));
+		andListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-t-andlist-withnomax"), createRequestDetails(rs));
 		def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(andListDef);
 		ourLog.info(def);
 		//@formatter:off
@@ -225,7 +225,7 @@ public class OperationServerWithSearchParamTypesDstu3Test {
 		));
 		//@formatter:on
 
-		OperationDefinition orListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient--orlist"), createRequestDetails(rs));
+		OperationDefinition orListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-t-orlist"), createRequestDetails(rs));
 		def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(orListDef);
 		ourLog.info(def);
 		//@formatter:off
@@ -241,7 +241,7 @@ public class OperationServerWithSearchParamTypesDstu3Test {
 		));
 		//@formatter:on
 		
-		orListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient--orlist-withnomax"), createRequestDetails(rs));
+		orListDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-t-orlist-withnomax"), createRequestDetails(rs));
 		def = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(orListDef);
 		ourLog.info(def);
 		//@formatter:off
@@ -490,7 +490,8 @@ public class OperationServerWithSearchParamTypesDstu3Test {
 		@Operation(name = "$orlist-withnomax", idempotent = true)
 		public Parameters orlistWithNoMax(
 				//@formatter:off
-				@OperationParam(name="valstr") List<StringOrListParam> theValStr,
+				@OperationParam(name="valstr"
+				) List<StringOrListParam> theValStr,
 				@OperationParam(name="valtok") List<TokenOrListParam> theValTok
 				//@formatter:on
 		) {
@@ -504,7 +505,7 @@ public class OperationServerWithSearchParamTypesDstu3Test {
 	}
 
 	private RequestDetails createRequestDetails(RestfulServer theServer) {
-		ServletRequestDetails retVal = new ServletRequestDetails(null);
+		ServletRequestDetails retVal = new ServletRequestDetails();
 		retVal.setServer(theServer);
 		return retVal;
 	}
diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/util/XmlUtilDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/util/XmlUtilDstu3Test.java
index 6cdee55aaf6..92cea006890 100644
--- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/util/XmlUtilDstu3Test.java
+++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/util/XmlUtilDstu3Test.java
@@ -81,9 +81,11 @@ public class XmlUtilDstu3Test {
 	public void testEncodePrettyPrint() throws IOException, SAXException, TransformerException {
 		String input = "<document><tag id=\"1\"/></document>";
 		Document parsed = XmlUtil.parseDocument(input);
-		String output = XmlUtil.encodeDocument(parsed, true);
+		String output = XmlUtil.encodeDocument(parsed, true)
+			.replace("\r\n", "\n")
+			.replaceAll("^ *", "");
 		assertEquals("<document>\n" +
-			"   <tag id=\"1\"/>\n" +
+			"<tag id=\"1\"/>\n" +
 			"</document>\n", output);
 	}
 
diff --git a/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerCapabilityStatementProviderDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerCapabilityStatementProviderDstu3Test.java
index 7de10333d3f..2074d19d5f2 100644
--- a/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerCapabilityStatementProviderDstu3Test.java
+++ b/hapi-fhir-structures-dstu3/src/test/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerCapabilityStatementProviderDstu3Test.java
@@ -338,55 +338,26 @@ public class ServerCapabilityStatementProviderDstu3Test {
 		String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
 		ourLog.info(conf);
 
-		assertEquals(4, conformance.getRest().get(0).getOperation().size());
+		assertEquals(2, conformance.getRest().get(0).getOperation().size());
 		List<String> operationNames = toOperationNames(conformance.getRest().get(0).getOperation());
-		assertThat(operationNames, containsInAnyOrder("someOp", "validate", "someOp", "validate"));
+		assertThat(operationNames, containsInAnyOrder("someOp", "validate"));
 
 		List<String> operationIdParts = toOperationIdParts(conformance.getRest().get(0).getOperation());
-		assertThat(operationIdParts, containsInAnyOrder("Patient-i-someOp", "Encounter-i-someOp", "Patient-i-validate", "Encounter-i-validate"));
+		assertThat(operationIdParts, containsInAnyOrder("EncounterPatient-i-someOp", "EncounterPatient-i-validate"));
 
 		{
-			OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-someOp"), createRequestDetails(rs));
+			OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/EncounterPatient-i-someOp"), createRequestDetails(rs));
 			validate(opDef);
 			ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
 			Set<String> types = toStrings(opDef.getResource());
 			assertEquals("someOp", opDef.getCode());
 			assertEquals(true, opDef.getInstance());
 			assertEquals(false, opDef.getSystem());
-			assertThat(types, containsInAnyOrder("Patient"));
+			assertThat(types, containsInAnyOrder("Patient", "Encounter"));
 			assertEquals(2, opDef.getParameter().size());
 			assertEquals("someOpParam1", opDef.getParameter().get(0).getName());
 			assertEquals("date", opDef.getParameter().get(0).getType());
 			assertEquals("someOpParam2", opDef.getParameter().get(1).getName());
-			assertEquals("Patient", opDef.getParameter().get(1).getType());
-		}
-		{
-			OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Encounter-i-someOp"), createRequestDetails(rs));
-			validate(opDef);
-			ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
-			Set<String> types = toStrings(opDef.getResource());
-			assertEquals("someOp", opDef.getCode());
-			assertEquals(true, opDef.getInstance());
-			assertEquals(false, opDef.getSystem());
-			assertThat(types, containsInAnyOrder("Encounter"));
-			assertEquals(2, opDef.getParameter().size());
-			assertEquals("someOpParam1", opDef.getParameter().get(0).getName());
-			assertEquals("date", opDef.getParameter().get(0).getType());
-			assertEquals("someOpParam2", opDef.getParameter().get(1).getName());
-			assertEquals("Encounter", opDef.getParameter().get(1).getType());
-		}
-		{
-			OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-validate"), createRequestDetails(rs));
-			validate(opDef);
-			ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
-			Set<String> types = toStrings(opDef.getResource());
-			assertEquals("validate", opDef.getCode());
-			assertEquals(true, opDef.getInstance());
-			assertEquals(false, opDef.getSystem());
-			assertThat(types, containsInAnyOrder("Patient"));
-			assertEquals(1, opDef.getParameter().size());
-			assertEquals("resource", opDef.getParameter().get(0).getName());
-			assertEquals("Patient", opDef.getParameter().get(0).getType());
 		}
 	}
 	
@@ -410,45 +381,6 @@ public class ServerCapabilityStatementProviderDstu3Test {
 
 	}
 
-	@Test
-	public void testOperationOnNoTypes() throws Exception {
-		RestfulServer rs = new RestfulServer(ourCtx);
-		rs.setProviders(new PlainProviderWithExtendedOperationOnNoType());
-
-		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider() {
-			@Override
-			public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
-				return super.getServerConformance(theRequest, createRequestDetails(rs));
-			}
-		};
-		rs.setServerConformanceProvider(sc);
-
-		rs.init(createServletConfig());
-
-		OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/-is-plain"), createRequestDetails(rs));
-		validate(opDef);
-
-		assertEquals("plain", opDef.getCode());
-		assertEquals(true, opDef.getIdempotent());
-		assertEquals(3, opDef.getParameter().size());
-
-		assertTrue(opDef.getParameter().get(0).hasName());
-		assertEquals("start", opDef.getParameter().get(0).getName());
-		assertEquals("in", opDef.getParameter().get(0).getUse().toCode());
-		assertEquals("0", opDef.getParameter().get(0).getMinElement().getValueAsString());
-		assertEquals("date", opDef.getParameter().get(0).getTypeElement().getValueAsString());
-
-		assertEquals("out1", opDef.getParameter().get(2).getName());
-		assertEquals("out", opDef.getParameter().get(2).getUse().toCode());
-		assertEquals("1", opDef.getParameter().get(2).getMinElement().getValueAsString());
-		assertEquals("2", opDef.getParameter().get(2).getMaxElement().getValueAsString());
-		assertEquals("string", opDef.getParameter().get(2).getTypeElement().getValueAsString());
-
-		assertThat(opDef.getSystem(), is(true));
-		assertThat(opDef.getType(), is(false));
-		assertThat(opDef.getInstance(), is(true));
-	}
-
 	@Test
 	public void testProviderWithRequiredAndOptional() throws Exception {
 
@@ -726,6 +658,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
 	}
 
 	@Test
+	@Disabled // This was working incorrectly previously
 	public void testSystemLevelNamedQueryWithParameters() throws Exception {
 		RestfulServer rs = new RestfulServer(ourCtx);
 		rs.setProviders(new NamedQueryPlainProvider());
@@ -1272,7 +1205,7 @@ public class ServerCapabilityStatementProviderDstu3Test {
   public static class PatientTripleSub extends PatientSubSub {}
 
 	private RequestDetails createRequestDetails(RestfulServer theServer) {
-		ServletRequestDetails retVal = new ServletRequestDetails(null);
+		ServletRequestDetails retVal = new ServletRequestDetails();
 		retVal.setServer(theServer);
 		return retVal;
 	}
diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml
index e842ad1c049..5f1386f1496 100644
--- a/hapi-fhir-structures-hl7org-dstu2/pom.xml
+++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/rest/server/ServerConformanceProvider.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/rest/server/ServerConformanceProvider.java
index c68ff5fd5f6..89108b0b695 100644
--- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/rest/server/ServerConformanceProvider.java
+++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/rest/server/ServerConformanceProvider.java
@@ -263,7 +263,7 @@ public class ServerConformanceProvider extends BaseServerCapabilityStatementProv
                 (SearchMethodBinding) nextMethodBinding, theRequestDetails);
           } else if (nextMethodBinding instanceof OperationMethodBinding) {
             OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
-            String opName = bindings.getOperationBindingToName().get(methodBinding);
+            String opName = bindings.getOperationBindingToId().get(methodBinding);
             if (operationNames.add(opName)) {
               // Only add each operation (by name) once
               rest.addOperation().setName(methodBinding.getName()).getDefinition()
@@ -299,7 +299,7 @@ public class ServerConformanceProvider extends BaseServerCapabilityStatementProv
           checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
           if (nextMethodBinding instanceof OperationMethodBinding) {
             OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
-            String opName = bindings.getOperationBindingToName().get(methodBinding);
+            String opName = bindings.getOperationBindingToId().get(methodBinding);
             if (operationNames.add(opName)) {
               rest.addOperation().setName(methodBinding.getName()).getDefinition()
                   .setReference("OperationDefinition/" + opName);
@@ -412,7 +412,7 @@ public class ServerConformanceProvider extends BaseServerCapabilityStatementProv
     if (theId == null || theId.hasIdPart() == false) {
       throw new ResourceNotFoundException(theId);
     }
-    List<OperationMethodBinding> sharedDescriptions = getServerConfiguration(theRequestDetails).provideBindings().getOperationNameToBindings().get(theId.getIdPart());
+    List<OperationMethodBinding> sharedDescriptions = getServerConfiguration(theRequestDetails).provideBindings().getOperationIdToBindings().get(theId.getIdPart());
     if (sharedDescriptions == null || sharedDescriptions.isEmpty()) {
       throw new ResourceNotFoundException(theId);
     }
diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationDuplicateServerHl7OrgDstu2Test.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationDuplicateServerHl7OrgDstu2Test.java
index 84a10a258f5..7aabf4fc2fa 100644
--- a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationDuplicateServerHl7OrgDstu2Test.java
+++ b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationDuplicateServerHl7OrgDstu2Test.java
@@ -56,14 +56,14 @@ public class OperationDuplicateServerHl7OrgDstu2Test {
 
       ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp));
 
-      assertEquals(3, resp.getRest().get(0).getOperation().size());
+      assertEquals(1, resp.getRest().get(0).getOperation().size());
       assertEquals("$myoperation", resp.getRest().get(0).getOperation().get(0).getName());
-      assertEquals("OperationDefinition/-s-myoperation", resp.getRest().get(0).getOperation().get(0).getDefinition().getReference());
+      assertEquals("OperationDefinition/OrganizationPatient-ts-myoperation", resp.getRest().get(0).getOperation().get(0).getDefinition().getReference());
     }
 
     // OperationDefinition
     {
-      HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/OperationDefinition/Patient--myoperation?_pretty=true");
+      HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/OperationDefinition/OrganizationPatient-ts-myoperation?_pretty=true");
       HttpResponse status = ourClient.execute(httpGet);
 
       assertEquals(200, status.getStatusLine().getStatusCode());
@@ -74,7 +74,7 @@ public class OperationDuplicateServerHl7OrgDstu2Test {
       OperationDefinition resp = ourCtx.newXmlParser().parseResource(OperationDefinition.class, response);
       assertEquals("$myoperation", resp.getCode());
       assertEquals(true, resp.getIdempotent());
-      assertEquals(1, resp.getType().size());
+      assertEquals(2, resp.getType().size());
       assertEquals(1, resp.getParameter().size());
     }
   }
diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderHl7OrgDstu2Test.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderHl7OrgDstu2Test.java
index fccaab41961..7108cb5fe1d 100644
--- a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderHl7OrgDstu2Test.java
+++ b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderHl7OrgDstu2Test.java
@@ -243,44 +243,6 @@ public class ServerConformanceProviderHl7OrgDstu2Test {
 
 	}
 
-	@Test
-	public void testOperationOnNoTypes() throws Exception {
-		RestfulServer rs = new RestfulServer(ourCtx);
-		rs.setProviders(new PlainProviderWithExtendedOperationOnNoType());
-
-		ServerConformanceProvider sc = new ServerConformanceProvider(rs) {
-			@Override
-			public Conformance getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
-				return super.getServerConformance(theRequest, theRequestDetails);
-			}
-		};
-		rs.setServerConformanceProvider(sc);
-
-		rs.init(createServletConfig());
-
-		Conformance sconf = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-		assertEquals("OperationDefinition/-is-plain", sconf.getRest().get(0).getOperation().get(0).getDefinition().getReference());
-
-		OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/-is-plain"), createRequestDetails(rs));
-
-		String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef);
-		ourLog.info(conf);
-
-		assertEquals("$plain", opDef.getCode());
-		assertEquals(true, opDef.getIdempotent());
-		assertEquals(3, opDef.getParameter().size());
-		assertEquals("start", opDef.getParameter().get(0).getName());
-		assertEquals("in", opDef.getParameter().get(0).getUse().toCode());
-		assertEquals("0", opDef.getParameter().get(0).getMinElement().getValueAsString());
-		assertEquals("date", opDef.getParameter().get(0).getTypeElement().getValueAsString());
-
-		assertEquals("out1", opDef.getParameter().get(2).getName());
-		assertEquals("out", opDef.getParameter().get(2).getUse().toCode());
-		assertEquals("1", opDef.getParameter().get(2).getMinElement().getValueAsString());
-		assertEquals("2", opDef.getParameter().get(2).getMaxElement().getValueAsString());
-		assertEquals("string", opDef.getParameter().get(2).getTypeElement().getValueAsString());
-}
-
 	@Test
 	public void testProviderWithRequiredAndOptional() throws Exception {
 
@@ -617,7 +579,7 @@ public class ServerConformanceProviderHl7OrgDstu2Test {
 	}
 
   private RequestDetails createRequestDetails(RestfulServer theServer) {
-    ServletRequestDetails retVal = new ServletRequestDetails(null);
+    ServletRequestDetails retVal = new ServletRequestDetails();
     retVal.setServer(theServer);
     return retVal;
   }
diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml
index d5f9e85f836..6bdf2b268dd 100644
--- a/hapi-fhir-structures-r4/pom.xml
+++ b/hapi-fhir-structures-r4/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/OperationServerR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/OperationServerR4Test.java
index 8ee22ee169b..84cfb5b1038 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/OperationServerR4Test.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/OperationServerR4Test.java
@@ -33,8 +33,18 @@ import org.eclipse.jetty.servlet.ServletHolder;
 import org.hl7.fhir.instance.model.api.IBaseResource;
 import org.hl7.fhir.instance.model.api.IIdType;
 import org.hl7.fhir.instance.model.api.IPrimitiveType;
-import org.hl7.fhir.r4.model.*;
+import org.hl7.fhir.r4.model.Binary;
+import org.hl7.fhir.r4.model.Bundle;
+import org.hl7.fhir.r4.model.CapabilityStatement;
+import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.IntegerType;
+import org.hl7.fhir.r4.model.MoneyQuantity;
+import org.hl7.fhir.r4.model.OperationDefinition;
 import org.hl7.fhir.r4.model.OperationDefinition.OperationParameterUse;
+import org.hl7.fhir.r4.model.Parameters;
+import org.hl7.fhir.r4.model.Patient;
+import org.hl7.fhir.r4.model.StringType;
+import org.hl7.fhir.r4.model.UnsignedIntType;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
@@ -72,6 +82,7 @@ public class OperationServerR4Test {
 	private static int ourPort;
 	private static Server ourServer;
 	private static IBaseResource ourNextResponse;
+	private static RestOperationTypeEnum ourLastRestOperation;
 	private IGenericClient myFhirClient;
 
 	@BeforeEach
@@ -98,11 +109,11 @@ public class OperationServerR4Test {
 		CapabilityStatement p = myFhirClient.fetchConformance().ofType(CapabilityStatement.class).prettyPrint().execute();
 		ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(p));
 
-		List<CapabilityStatement.CapabilityStatementRestResourceOperationComponent> ops = p.getRest().get(0).getOperation();
+		List<CapabilityStatement.CapabilityStatementRestResourceOperationComponent> ops = p.getRestFirstRep().getResource().stream().filter(t->t.getType().equals("Patient")).findFirst().orElseThrow(()->new IllegalArgumentException()).getOperation();
 		assertThat(ops.size(), greaterThan(1));
 
 		List<String> opNames = toOpNames(ops);
-		assertThat(opNames, containsInRelativeOrder("OP_TYPE"));
+		assertThat(opNames.toString(), opNames, containsInRelativeOrder("OP_TYPE"));
 
 		OperationDefinition def = myFhirClient.read().resource(OperationDefinition.class).withId(ops.get(opNames.indexOf("OP_TYPE")).getDefinition()).execute();
 		assertEquals("OP_TYPE", def.getCode());
@@ -113,7 +124,7 @@ public class OperationServerR4Test {
 	 */
 	@Test
 	public void testOperationDefinition() {
-		OperationDefinition def = myFhirClient.read().resource(OperationDefinition.class).withId("OperationDefinition/Patient--OP_TYPE").execute();
+		OperationDefinition def = myFhirClient.read().resource(OperationDefinition.class).withId("OperationDefinition/Patient-t-OP_TYPE").execute();
 
 		ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(def));
 
@@ -179,8 +190,6 @@ public class OperationServerR4Test {
 
 	}
 
-
-
 	@Test
 	public void testManualResponseWithPrimitiveParam() throws Exception {
 
@@ -197,7 +206,6 @@ public class OperationServerR4Test {
 
 	}
 
-
 	@Test
 	public void testInstanceEverythingGet() throws Exception {
 
@@ -230,7 +238,6 @@ public class OperationServerR4Test {
 		assertEquals(RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE, ourLastRestOperation);
 	}
 
-
 	@Test
 	public void testInstanceEverythingHapiClient() {
 		ourCtx.newRestfulGenericClient("http://localhost:" + ourPort).operation().onInstance(new IdType("Patient/123")).named("$everything").withParameters(new Parameters()).execute();
@@ -278,7 +285,7 @@ public class OperationServerR4Test {
 
 	@Test
 	public void testManualInputAndOutput() throws Exception {
-		byte[] bytes = new byte[]{1,2,3,4,5,6,7,8,7,6,5,4,3,2,1};
+		byte[] bytes = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1};
 		ContentType contentType = ContentType.IMAGE_PNG;
 
 		HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$manualInputAndOutput");
@@ -295,10 +302,9 @@ public class OperationServerR4Test {
 
 	}
 
-
 	@Test
 	public void testManualInputAndOutputWithUrlParam() throws Exception {
-		byte[] bytes = new byte[]{1,2,3,4,5,6,7,8,7,6,5,4,3,2,1};
+		byte[] bytes = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1};
 		ContentType contentType = ContentType.IMAGE_PNG;
 
 		HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$manualInputAndOutputWithParam?param1=value");
@@ -838,7 +844,7 @@ public class OperationServerR4Test {
 			return new Bundle();
 		}
 
-		@Operation(name="$manualInputAndOutput", manualResponse=true, manualRequest=true)
+		@Operation(name = "$manualInputAndOutput", manualResponse = true, manualRequest = true)
 		public void manualInputAndOutput(HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws IOException {
 			String contentType = theServletRequest.getContentType();
 			byte[] bytes = IOUtils.toByteArray(theServletRequest.getInputStream());
@@ -850,9 +856,9 @@ public class OperationServerR4Test {
 			theServletResponse.getOutputStream().close();
 		}
 
-		@Operation(name="$manualInputAndOutputWithParam", manualResponse=true, manualRequest=true)
+		@Operation(name = "$manualInputAndOutputWithParam", manualResponse = true, manualRequest = true)
 		public void manualInputAndOutputWithParam(
-			@OperationParam(name="param1") StringType theParam1,
+			@OperationParam(name = "param1") StringType theParam1,
 			HttpServletRequest theServletRequest,
 			HttpServletResponse theServletResponse
 		) throws IOException {
@@ -880,7 +886,6 @@ public class OperationServerR4Test {
 		}
 
 	}
-	private static RestOperationTypeEnum ourLastRestOperation;
 
 	public static class PlainProvider {
 
@@ -918,10 +923,10 @@ public class OperationServerR4Test {
 			return new SimpleBundleProvider(resources);
 		}
 
-		@Operation(name= "$manualResponseWithPrimitiveParam", idempotent = true, global = true, manualResponse = true)
+		@Operation(name = "$manualResponseWithPrimitiveParam", idempotent = true, global = true, manualResponse = true)
 		public void manualResponseWithPrimitiveParam(
 			@IdParam IIdType theResourceId,
-			@OperationParam(name="path", min = 1, max = 1) IPrimitiveType<String> thePath,
+			@OperationParam(name = "path", min = 1, max = 1) IPrimitiveType<String> thePath,
 			ServletRequestDetails theRequestDetails,
 			HttpServletRequest theServletRequest,
 			HttpServletResponse theServletResponse) {
@@ -933,7 +938,7 @@ public class OperationServerR4Test {
 			theServletResponse.setStatus(200);
 		}
 
-			@Operation(name = "$OP_SERVER")
+		@Operation(name = "$OP_SERVER")
 		public Parameters opServer(
 			@OperationParam(name = "PARAM1") StringType theParam1,
 			@OperationParam(name = "PARAM2") Patient theParam2
@@ -1028,12 +1033,4 @@ public class OperationServerR4Test {
 
 	}
 
-	public static void main(String[] theValue) {
-		Parameters p = new Parameters();
-		p.addParameter().setName("start").setValue(new DateTimeType("2001-01-02"));
-		p.addParameter().setName("end").setValue(new DateTimeType("2015-07-10"));
-		String inParamsStr = FhirContext.forDstu2().newXmlParser().encodeResourceToString(p);
-		ourLog.info(inParamsStr.replace("\"", "\\\""));
-	}
-
 }
diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/RestfulServerTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/RestfulServerTest.java
similarity index 67%
rename from hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/RestfulServerTest.java
rename to hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/RestfulServerTest.java
index 95c67bec9bb..679c1ec3a6f 100644
--- a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/RestfulServerTest.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/RestfulServerTest.java
@@ -3,7 +3,7 @@ package ca.uhn.fhir.rest.server;
 import ca.uhn.fhir.context.ConfigurationException;
 import ca.uhn.fhir.context.FhirContext;
 import ca.uhn.fhir.context.FhirVersionEnum;
-import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.model.api.annotation.ResourceDef;
 import ca.uhn.fhir.rest.annotation.Create;
 import ca.uhn.fhir.rest.annotation.Metadata;
 import ca.uhn.fhir.rest.annotation.Operation;
@@ -16,11 +16,10 @@ import org.hl7.fhir.instance.model.api.IBaseConformance;
 import org.hl7.fhir.instance.model.api.IBaseMetaType;
 import org.hl7.fhir.instance.model.api.IBaseResource;
 import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.Patient;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Answers;
-import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
 import javax.servlet.ServletException;
@@ -33,70 +32,55 @@ import static org.mockito.Mockito.*;
 
 @ExtendWith(MockitoExtension.class)
 public class RestfulServerTest {
-	@Mock(answer = Answers.RETURNS_DEEP_STUBS)
-	private FhirContext myCtx;
-	private RestfulServer restfulServer;
+	private final FhirContext myCtx = FhirContext.forR4(); // don't use cached, we register custom resources
+	private RestfulServer myRestfulServer;
 
 	@BeforeEach
 	public void setUp() throws ServletException {
-		when(myCtx.getVersion().getVersion()).thenReturn(FhirVersionEnum.DSTU3);
-		when(myCtx.getVersion().getServerVersion()).thenReturn(new MyFhirVersionServer());
-
-		restfulServer = new RestfulServer(myCtx);
-		restfulServer.init();
-	}
-
-	private void mockResource(Class theClass) {
-		RuntimeResourceDefinition resourceDefinitionMock = mock(RuntimeResourceDefinition.class);
-		String className = theClass.getSimpleName();
-		lenient().when(resourceDefinitionMock.getName()).thenReturn(className);
-		lenient().when(myCtx.getResourceDefinition(className)).thenReturn(resourceDefinitionMock);
-		lenient().when(myCtx.getResourceType(theClass)).thenReturn(className);
+		myRestfulServer = new RestfulServer(myCtx);
+		myRestfulServer.init();
 	}
 
 	@Test
 	public void testRegisterProvidersWithMethodBindings() {
-		mockResource(MyResource.class);
-		mockResource(MyResource2.class);
-
 		MyProvider provider = new MyProvider();
-		restfulServer.registerProvider(provider);
+		myRestfulServer.registerProvider(provider);
 		MyProvider2 provider2 = new MyProvider2();
-		restfulServer.registerProvider(provider2);
+		myRestfulServer.registerProvider(provider2);
 
-		assertFalse(restfulServer.getProviderMethodBindings(provider).isEmpty());
-		assertFalse(restfulServer.getProviderMethodBindings(provider2).isEmpty());
+		assertFalse(myRestfulServer.getProviderMethodBindings(provider).isEmpty());
+		assertFalse(myRestfulServer.getProviderMethodBindings(provider2).isEmpty());
 
-		restfulServer.unregisterProvider(provider);
-		assertTrue(restfulServer.getProviderMethodBindings(provider).isEmpty());
-		assertFalse(restfulServer.getProviderMethodBindings(provider2).isEmpty());
+		myRestfulServer.unregisterProvider(provider);
+		assertTrue(myRestfulServer.getProviderMethodBindings(provider).isEmpty());
+		assertFalse(myRestfulServer.getProviderMethodBindings(provider2).isEmpty());
 	}
 
 	@Test
 	public void testRegisterProviders() {
 		//test register Plain Provider
-		restfulServer.registerProvider(new MyClassWithRestInterface());
-		assertEquals(1, restfulServer.getPlainProviders().size());
-		Object plainProvider = restfulServer.getPlainProviders().iterator().next();
+		myRestfulServer.registerProvider(new MyClassWithRestInterface());
+		assertEquals(1, myRestfulServer.getResourceProviders().size());
+		Object plainProvider = myRestfulServer.getResourceProviders().get(0);
 		assertTrue(plainProvider instanceof MyClassWithRestInterface);
 
 		//test register Resource Provider
-		restfulServer.registerProvider(new MyResourceProvider());
-		assertEquals(1, restfulServer.getResourceProviders().size());
-		IResourceProvider resourceProvider = restfulServer.getResourceProviders().iterator().next();
+		myRestfulServer.registerProvider(new MyResourceProvider());
+		assertEquals(2, myRestfulServer.getResourceProviders().size());
+		IResourceProvider resourceProvider = myRestfulServer.getResourceProviders().get(1);
 		assertTrue(resourceProvider instanceof MyResourceProvider);
 
 		//test unregister providers
-		restfulServer.unregisterProvider(plainProvider);
-		assertTrue(restfulServer.getPlainProviders().isEmpty());
-		restfulServer.unregisterProvider(resourceProvider);
-		assertTrue(restfulServer.getResourceProviders().isEmpty());
+		myRestfulServer.unregisterProvider(plainProvider);
+		assertFalse(myRestfulServer.getResourceProviders().isEmpty());
+		myRestfulServer.unregisterProvider(resourceProvider);
+		assertTrue(myRestfulServer.getResourceProviders().isEmpty());
 	}
 
 	@Test
 	public void testFailRegisterInterfaceProviderWithoutRestfulMethod() {
 		try {
-			restfulServer.registerProvider(new MyClassWithoutRestInterface());
+			myRestfulServer.registerProvider(new MyClassWithoutRestInterface());
 			fail();
 		} catch (ConfigurationException e) {
 			assertEquals("Did not find any annotated RESTful methods on provider class ca.uhn.fhir.rest.server.RestfulServerTest$MyClassWithoutRestInterface", e.getMessage());
@@ -108,7 +92,11 @@ public class RestfulServerTest {
 	private static class MyClassWithoutRestInterface implements Serializable {
 	}
 
-	private static class MyClassWithRestInterface implements MyRestInterface {
+	private static class MyClassWithRestInterface implements MyRestInterface, IResourceProvider {
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return Patient.class;
+		}
 	}
 
 	@SuppressWarnings("unused")
@@ -148,7 +136,7 @@ public class RestfulServerTest {
 
 		@Override
 		public Class<? extends IBaseResource> getResourceType() {
-			return IBaseResource.class;
+			return Patient.class;
 		}
 	}
 
@@ -176,7 +164,8 @@ public class RestfulServerTest {
 		}
 	}
 
-	private static class MyResource implements IBaseResource {
+	@ResourceDef(name="MyResource")
+	public static class MyResource implements IBaseResource {
 
 		@Override
 		public boolean isEmpty() {
@@ -230,11 +219,12 @@ public class RestfulServerTest {
 
 		@Override
 		public FhirVersionEnum getStructureFhirVersionEnum() {
-			return null;
+			return FhirVersionEnum.R4;
 		}
 	}
 
-	private static class MyResource2 extends MyResource {
+	@ResourceDef(name="MyResource2")
+	public static class MyResource2 extends MyResource {
 	}
 
 }
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionR4Test.java
index 57e485b792a..eac229a77c8 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionR4Test.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionR4Test.java
@@ -2,7 +2,9 @@ package ca.uhn.fhir.rest.server;
 
 import ca.uhn.fhir.context.ConfigurationException;
 import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.FhirVersionEnum;
 import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
+import ca.uhn.fhir.rest.annotation.IdParam;
 import ca.uhn.fhir.rest.annotation.Operation;
 import ca.uhn.fhir.rest.annotation.OperationParam;
 import ca.uhn.fhir.rest.annotation.OptionalParam;
@@ -11,24 +13,35 @@ import ca.uhn.fhir.rest.annotation.Search;
 import ca.uhn.fhir.rest.annotation.Update;
 import ca.uhn.fhir.rest.annotation.Validate;
 import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
 import ca.uhn.fhir.rest.param.TokenParam;
 import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.provider.ServerCapabilityStatementProvider;
+import ca.uhn.fhir.test.utilities.server.MockServletUtil;
 import ca.uhn.fhir.util.TestUtil;
 import com.google.common.collect.Lists;
 import org.hamcrest.core.StringContains;
 import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.r4.model.CapabilityStatement;
+import org.hl7.fhir.r4.model.DateType;
+import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.OperationDefinition;
 import org.hl7.fhir.r4.model.Patient;
 import org.hl7.fhir.r4.model.StringType;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Test;
 
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
 
 import java.util.List;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 public class ServerInvalidDefinitionR4Test extends BaseR4ServerTest {
@@ -182,9 +195,31 @@ public class ServerInvalidDefinitionR4Test extends BaseR4ServerTest {
 
 	}
 
-	@AfterAll
-	public static void afterClassClearContext() {
-		TestUtil.clearAllStaticFieldsForUnitTest();
+	@Test
+	public void testOperationOnNoTypes() throws Exception {
+		@SuppressWarnings("unused")
+		class PlainProviderWithExtendedOperationOnNoType {
+
+			@Operation(name = "plain", idempotent = true, returnParameters = {@OperationParam(min = 1, max = 2, name = "out1", type = StringType.class)})
+			public IBundleProvider everything(HttpServletRequest theServletRequest, @IdParam IdType theId, @OperationParam(name = "start") DateType theStart,
+														 @OperationParam(name = "end") DateType theEnd) {
+				return null;
+			}
+
+		}
+
+		RestfulServer rs = new RestfulServer(FhirContext.forCached(FhirVersionEnum.R4));
+		rs.setProviders(new PlainProviderWithExtendedOperationOnNoType());
+		rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
+
+		try {
+			rs.init(MockServletUtil.createServletConfig());
+			fail();
+		} catch (ServletException e) {
+			assertEquals("Failed to initialize FHIR Restful server: Failure scanning class PlainProviderWithExtendedOperationOnNoType: @Operation method is an instance level method (it has an @IdParam parameter) but is not marked as global() and is not declared in a resource provider: everything", e.getMessage());
+		}
+
 	}
 
+
 }
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorR4Test.java
index 480568284fb..3bb305e4211 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorR4Test.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorR4Test.java
@@ -302,8 +302,37 @@ public class AuthorizationInterceptorR4Test {
 		assertEquals(200, status.getStatusLine().getStatusCode());
 		assertTrue(ourHitMethod);
 
+		ourHitMethod = false;
+		httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$validate");
+		status = ourClient.execute(httpGet);
+		extractResponseAndClose(status);
+		assertEquals(200, status.getStatusLine().getStatusCode());
+		assertTrue(ourHitMethod);
 	}
 
+
+	/**
+	 * A GET to the base URL isn't valid, but the interceptor should allow it
+	 */
+	@Test
+	public void testGetRoot() throws Exception {
+		ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
+			@Override
+			public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
+				return new RuleBuilder()
+					.allowAll()
+					.build();
+			}
+		});
+
+		HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/");
+		CloseableHttpResponse status = ourClient.execute(httpGet);
+		extractResponseAndClose(status);
+		assertEquals(400, status.getStatusLine().getStatusCode());
+
+	}
+
+
 	@Test
 	public void testAllowAllForTenant() throws Exception {
 		ourServlet.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
@@ -1944,7 +1973,6 @@ public class AuthorizationInterceptorR4Test {
 
 		HttpGet httpGet;
 		HttpResponse status;
-		String response;
 
 		ourReturn = Collections.singletonList(createPatient(2));
 		ourHitMethod = false;
@@ -1968,7 +1996,6 @@ public class AuthorizationInterceptorR4Test {
 
 		HttpGet httpGet;
 		HttpResponse status;
-		String response;
 
 		ourReturn = Collections.singletonList(createPatient(2));
 		ourHitMethod = false;
@@ -2188,7 +2215,6 @@ public class AuthorizationInterceptorR4Test {
 
 		HttpGet httpGet;
 		HttpResponse status;
-		String response;
 
 		ourReturn = Collections.singletonList(new Consent().setDateTime(new Date()).setId("Consent/123"));
 		ourHitMethod = false;
@@ -2933,7 +2959,7 @@ public class AuthorizationInterceptorR4Test {
 		HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
 		httpPost.setEntity(createFhirResourceEntity(requestBundle));
 		CloseableHttpResponse status = ourClient.execute(httpPost);
-		String resp = extractResponseAndClose(status);
+		extractResponseAndClose(status);
 		assertEquals(200, status.getStatusLine().getStatusCode());
 
 	}
diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml
index 490ba126870..3a1dbc48cb6 100644
--- a/hapi-fhir-structures-r5/pom.xml
+++ b/hapi-fhir-structures-r5/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-structures-r5/src/test/java/ca/uhn/fhir/rest/server/ServerCapabilityStatementProviderR5Test.java b/hapi-fhir-structures-r5/src/test/java/ca/uhn/fhir/rest/server/ServerCapabilityStatementProviderR5Test.java
index 19f0ca0dbe7..d1940e3649f 100644
--- a/hapi-fhir-structures-r5/src/test/java/ca/uhn/fhir/rest/server/ServerCapabilityStatementProviderR5Test.java
+++ b/hapi-fhir-structures-r5/src/test/java/ca/uhn/fhir/rest/server/ServerCapabilityStatementProviderR5Test.java
@@ -125,7 +125,7 @@ public class ServerCapabilityStatementProviderR5Test {
 	}
 
 	private RequestDetails createRequestDetails(RestfulServer theServer) {
-		ServletRequestDetails retVal = new ServletRequestDetails(null);
+		ServletRequestDetails retVal = new ServletRequestDetails();
 		retVal.setServer(theServer);
 		return retVal;
 	}
@@ -147,8 +147,9 @@ public class ServerCapabilityStatementProviderR5Test {
 		String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
 		ourLog.info(conf);
 
-		assertEquals(1, conformance.getRest().get(0).getOperation().size());
-		assertEquals("everything", conformance.getRest().get(0).getOperation().get(0).getName());
+		List<CapabilityStatementRestResourceOperationComponent> operations = conformance.getRestFirstRep().getResource().stream().filter(t->t.getType().equals("Patient")).findFirst().orElseThrow(()->new IllegalArgumentException()).getOperation();
+		assertEquals(1, operations.size());
+		assertEquals("everything", operations.get(0).getName());
 
 		OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
 		validate(opDef);
@@ -163,6 +164,7 @@ public class ServerCapabilityStatementProviderR5Test {
 
 		RestfulServer rs = new RestfulServer(myCtx);
 		rs.setProviders(new ProviderWithExtendedOperationReturningBundle());
+		rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
 
 		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
 		};
@@ -291,56 +293,42 @@ public class ServerCapabilityStatementProviderR5Test {
 
 		String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
 		ourLog.info(conf);
+		List<CapabilityStatementRestResourceOperationComponent> operations;
 
-		assertEquals(4, conformance.getRest().get(0).getOperation().size());
-		List<String> operationNames = toOperationNames(conformance.getRest().get(0).getOperation());
-		assertThat(operationNames, containsInAnyOrder("someOp", "validate", "someOp", "validate"));
-
-		List<String> operationIdParts = toOperationIdParts(conformance.getRest().get(0).getOperation());
-		assertThat(operationIdParts, containsInAnyOrder("Patient-i-someOp", "Encounter-i-someOp", "Patient-i-validate", "Encounter-i-validate"));
+		operations = conformance.getRestFirstRep().getResource().stream().filter(t->t.getType().equals("Patient")).findFirst().orElseThrow(()->new IllegalArgumentException()).getOperation();
+		assertEquals(2, operations.size());
+		List<String> operationNames = toOperationNames(operations);
+		assertThat(operationNames.toString(), operationNames, containsInAnyOrder("someOp", "validate"));
+		List<String> operationIdParts = toOperationIdParts(operations);
+		assertThat(operationIdParts.toString(), operationIdParts, containsInAnyOrder("EncounterPatient-i-someOp", "EncounterPatient-i-validate"));
 
 		{
-			OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-someOp"), createRequestDetails(rs));
+			OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/EncounterPatient-i-someOp"), createRequestDetails(rs));
 			validate(opDef);
 			ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
 			Set<String> types = toStrings(opDef.getResource());
 			assertEquals("someOp", opDef.getCode());
 			assertEquals(true, opDef.getInstance());
 			assertEquals(false, opDef.getSystem());
-			assertThat(types, containsInAnyOrder("Patient"));
+			assertThat(types, containsInAnyOrder("Patient", "Encounter"));
 			assertEquals(2, opDef.getParameter().size());
 			assertEquals("someOpParam1", opDef.getParameter().get(0).getName());
 			assertEquals("date", opDef.getParameter().get(0).getType().toCode());
 			assertEquals("someOpParam2", opDef.getParameter().get(1).getName());
-			assertEquals("Patient", opDef.getParameter().get(1).getType().toCode());
+			assertEquals("Resource", opDef.getParameter().get(1).getType().toCode());
 		}
 		{
-			OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Encounter-i-someOp"), createRequestDetails(rs));
-			validate(opDef);
-			ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
-			Set<String> types = toStrings(opDef.getResource());
-			assertEquals("someOp", opDef.getCode());
-			assertEquals(true, opDef.getInstance());
-			assertEquals(false, opDef.getSystem());
-			assertThat(types, containsInAnyOrder("Encounter"));
-			assertEquals(2, opDef.getParameter().size());
-			assertEquals("someOpParam1", opDef.getParameter().get(0).getName());
-			assertEquals("date", opDef.getParameter().get(0).getType().toCode());
-			assertEquals("someOpParam2", opDef.getParameter().get(1).getName());
-			assertEquals("Encounter", opDef.getParameter().get(1).getType().toCode());
-		}
-		{
-			OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-validate"), createRequestDetails(rs));
+			OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/EncounterPatient-i-validate"), createRequestDetails(rs));
 			validate(opDef);
 			ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
 			Set<String> types = toStrings(opDef.getResource());
 			assertEquals("validate", opDef.getCode());
 			assertEquals(true, opDef.getInstance());
 			assertEquals(false, opDef.getSystem());
-			assertThat(types, containsInAnyOrder("Patient"));
+			assertThat(types, containsInAnyOrder("Patient", "Encounter"));
 			assertEquals(1, opDef.getParameter().size());
 			assertEquals("resource", opDef.getParameter().get(0).getName());
-			assertEquals("Patient", opDef.getParameter().get(0).getType().toCode());
+			assertEquals("Resource", opDef.getParameter().get(0).getType().toCode());
 		}
 	}
 
@@ -364,45 +352,6 @@ public class ServerCapabilityStatementProviderR5Test {
 
 	}
 
-	@Test
-	public void testOperationOnNoTypes() throws Exception {
-		RestfulServer rs = new RestfulServer(myCtx);
-		rs.setProviders(new PlainProviderWithExtendedOperationOnNoType());
-
-		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
-			@Override
-			public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
-				return (CapabilityStatement) super.getServerConformance(theRequest, createRequestDetails(rs));
-			}
-		};
-		rs.setServerConformanceProvider(sc);
-
-		rs.init(createServletConfig());
-
-		OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/-is-plain"), createRequestDetails(rs));
-		validate(opDef);
-
-		assertEquals("plain", opDef.getCode());
-		assertEquals(false, opDef.getAffectsState());
-		assertEquals(3, opDef.getParameter().size());
-
-		assertTrue(opDef.getParameter().get(0).hasName());
-		assertEquals("start", opDef.getParameter().get(0).getName());
-		assertEquals("in", opDef.getParameter().get(0).getUse().toCode());
-		assertEquals("0", opDef.getParameter().get(0).getMinElement().getValueAsString());
-		assertEquals("date", opDef.getParameter().get(0).getTypeElement().getValueAsString());
-
-		assertEquals("out1", opDef.getParameter().get(2).getName());
-		assertEquals("out", opDef.getParameter().get(2).getUse().toCode());
-		assertEquals("1", opDef.getParameter().get(2).getMinElement().getValueAsString());
-		assertEquals("2", opDef.getParameter().get(2).getMaxElement().getValueAsString());
-		assertEquals("string", opDef.getParameter().get(2).getTypeElement().getValueAsString());
-
-		assertThat(opDef.getSystem(), is(true));
-		assertThat(opDef.getType(), is(false));
-		assertThat(opDef.getInstance(), is(true));
-	}
-
 	@Test
 	public void testProviderWithRequiredAndOptional() throws Exception {
 
@@ -706,7 +655,7 @@ public class ServerCapabilityStatementProviderR5Test {
 		ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
 		validate(operationDefinition);
 		assertThat(operationDefinition.getCode(), is(NamedQueryPlainProvider.QUERY_NAME));
-		assertThat("The operation name should be the description, if a description is set", operationDefinition.getName(), equalTo("Search_testQuery"));
+		assertThat("The operation name should be the description, if a description is set", operationDefinition.getName(), equalTo("TestQuery"));
 		assertThat(operationDefinition.getStatus(), is(PublicationStatus.ACTIVE));
 		assertThat(operationDefinition.getKind(), is(OperationKind.QUERY));
 		assertThat(operationDefinition.getDescription(), is(NamedQueryPlainProvider.DESCRIPTION));
@@ -740,15 +689,15 @@ public class ServerCapabilityStatementProviderR5Test {
 		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
 		ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
 
-		CapabilityStatementRestComponent restComponent = conformance.getRest().get(0);
-		CapabilityStatementRestResourceOperationComponent operationComponent = restComponent.getOperation().get(0);
+		CapabilityStatementRestResourceComponent resource = conformance.getRestFirstRep().getResource().stream().filter(t->t.getType().equals("Patient")).findFirst().orElseThrow(()->new IllegalArgumentException());
+		CapabilityStatementRestResourceOperationComponent operationComponent = resource.getOperation().get(0);
 		String operationReference = operationComponent.getDefinition();
 		assertThat(operationReference, not(nullValue()));
 
 		OperationDefinition operationDefinition = (OperationDefinition) sc.readOperationDefinition(new IdType(operationReference), createRequestDetails(rs));
 		ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
 		validate(operationDefinition);
-		assertThat("The operation name should be the code if no description is set", operationDefinition.getName(), equalTo("Search_testQuery"));
+		assertThat("The operation name should be the code if no description is set", operationDefinition.getName(), equalTo("TestQuery"));
 		String patientResourceName = "Patient";
 		assertThat("A resource level search targets the resource of the provider it's defined in", operationDefinition.getResource().get(0).getValue(), is(patientResourceName));
 		assertThat(operationDefinition.getSystem(), is(false));
@@ -764,11 +713,7 @@ public class ServerCapabilityStatementProviderR5Test {
 		assertThat(param.getMax(), is("1"));
 		assertThat(param.getUse(), is(Enumerations.OperationParameterUse.IN));
 
-		CapabilityStatementRestResourceComponent patientResource = restComponent.getResource().stream()
-				.filter(r -> patientResourceName.equals(r.getType()))
-				.findAny()
-				.get();
-		assertThat("Named query parameters should not appear in the resource search params", patientResource.getSearchParam(), is(empty()));
+		assertThat("Named query parameters should not appear in the resource search params", resource.getSearchParam(), is(empty()));
 	}
 
 	@Test
@@ -787,7 +732,7 @@ public class ServerCapabilityStatementProviderR5Test {
 		String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
 		ourLog.info(conf);
 
-		List<CapabilityStatementRestResourceOperationComponent> operations = conformance.getRest().get(0).getOperation();
+		List<CapabilityStatementRestResourceOperationComponent> operations = conformance.getRestFirstRep().getResource().stream().filter(t->t.getType().equals("Patient")).findFirst().orElseThrow(()->new IllegalArgumentException()).getOperation();
 		assertThat(operations.size(), is(1));
 		assertThat(operations.get(0).getName(), is(TypeLevelOperationProvider.OPERATION_NAME));
 
diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml
index 66f0f0ac9fd..609c1ab8515 100644
--- a/hapi-fhir-test-utilities/pom.xml
+++ b/hapi-fhir-test-utilities/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
@@ -60,7 +60,28 @@
 			<optional>true</optional>
 		</dependency>
 
-		<!-- Jetty -->
+        <!-- HTMLUnit -->
+        <dependency>
+            <groupId>net.sourceforge.htmlunit</groupId>
+            <artifactId>htmlunit</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>xml-apis</groupId>
+                    <artifactId>xml-apis</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>xerces</groupId>
+                    <artifactId>xercesImpl</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+        </dependency>
+
+        <!-- Jetty -->
 		<dependency>
 			<groupId>org.eclipse.jetty</groupId>
 			<artifactId>jetty-servlet</artifactId>
@@ -104,10 +125,18 @@
 					<groupId>org.hamcrest</groupId>
 					<artifactId>hamcrest-core</artifactId>
 				</exclusion>
+                <exclusion>
+                    <groupId>junit</groupId>
+                    <artifactId>junit</artifactId>
+                </exclusion>
 			</exclusions>
 		</dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+        </dependency>
 
-	</dependencies>
+    </dependencies>
 
 	<build>
 		<plugins>
diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/HtmlUtil.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/HtmlUtil.java
new file mode 100644
index 00000000000..700b33d9551
--- /dev/null
+++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/HtmlUtil.java
@@ -0,0 +1,56 @@
+package ca.uhn.fhir.test.utilities;
+
+/*-
+ * #%L
+ * HAPI FHIR Test Utilities
+ * %%
+ * Copyright (C) 2014 - 2021 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import com.gargoylesoftware.htmlunit.BrowserVersion;
+import com.gargoylesoftware.htmlunit.StringWebResponse;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlForm;
+import com.gargoylesoftware.htmlunit.html.HtmlInput;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.html.parser.neko.HtmlUnitNekoHtmlParser;
+import org.awaitility.Awaitility;
+
+import java.io.IOException;
+import java.net.URL;
+
+public class HtmlUtil {
+
+	public static HtmlPage parseAsHtml(String theRespString, URL theUrl) throws IOException {
+		StringWebResponse response = new StringWebResponse(theRespString, theUrl);
+		WebClient client = new WebClient(BrowserVersion.BEST_SUPPORTED, false, null, -1);
+		client.getOptions().setCssEnabled(false);
+		client.getOptions().setJavaScriptEnabled(false);
+
+		final HtmlPage page = new HtmlPage(response, client.getCurrentWindow());
+		HtmlUnitNekoHtmlParser htmlUnitNekoHtmlParser = new HtmlUnitNekoHtmlParser();
+		htmlUnitNekoHtmlParser.parse(response, page, false);
+		return page;
+	}
+
+	public static HtmlForm waitForForm(HtmlPage thePage, String theName) {
+		return Awaitility.await().until(() -> thePage.getFormByName(theName), t -> t != null);
+	}
+
+	public static HtmlInput waitForInput(HtmlForm theForm, String theName) {
+		return Awaitility.await().until(() -> theForm.getInputByName(theName), t -> t != null);
+	}
+}
diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/JettyUtil.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/JettyUtil.java
index 86d13644749..fae4d26807e 100644
--- a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/JettyUtil.java
+++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/JettyUtil.java
@@ -28,6 +28,9 @@ import org.eclipse.jetty.server.handler.StatisticsHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 public class JettyUtil {
     
     /**
@@ -43,18 +46,20 @@ public class JettyUtil {
     /**
      * Starts the given Jetty server, and configures it for graceful shutdown
      */
-    public static void startServer(Server server) throws Exception {
+    public static void startServer(@Nonnull Server theServer) throws Exception {
         //Needed for graceful shutdown, see https://github.com/eclipse/jetty.project/issues/2076#issuecomment-353717761
-        server.insertHandler(new StatisticsHandler());
-        server.start();
+        theServer.insertHandler(new StatisticsHandler());
+        theServer.start();
     }
     
     /**
-     * Shut down the given Jetty server, and release held resources. 
+     * Shut down the given Jetty server, and release held resources.
      */
-    public static void closeServer(Server server) throws Exception {
-        server.stop();
-        server.destroy();
+    public static void closeServer(@Nullable Server theServer) throws Exception {
+    	if (theServer != null) {
+			theServer.stop();
+			theServer.destroy();
+		}
     }
     
 }
diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/MockServletUtil.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/MockServletUtil.java
new file mode 100644
index 00000000000..52c92c115ea
--- /dev/null
+++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/MockServletUtil.java
@@ -0,0 +1,42 @@
+package ca.uhn.fhir.test.utilities.server;
+
+/*-
+ * #%L
+ * HAPI FHIR Test Utilities
+ * %%
+ * Copyright (C) 2014 - 2021 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import javax.servlet.ServletConfig;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MockServletUtil {
+
+	/**
+	 * Non instantiable
+	 */
+	private MockServletUtil() {
+		super();
+	}
+
+	public static ServletConfig createServletConfig() {
+		ServletConfig sc = mock(ServletConfig.class);
+		when(sc.getServletContext()).thenReturn(null);
+		return sc;
+	}
+}
diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerExtension.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerExtension.java
index e735dc37c7a..4a8b02f9131 100644
--- a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerExtension.java
+++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerExtension.java
@@ -32,6 +32,7 @@ import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
 import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.junit.jupiter.api.extension.AfterEachCallback;
@@ -54,10 +55,11 @@ public class RestfulServerExtension implements BeforeEachCallback, AfterEachCall
 	private FhirVersionEnum myFhirVersion;
 	private Server myServer;
 	private RestfulServer myServlet;
-	private int myPort;
+	private int myPort = 0;
 	private CloseableHttpClient myHttpClient;
 	private IGenericClient myFhirClient;
 	private List<Consumer<RestfulServer>> myConsumers = new ArrayList<>();
+	private String myServletPath = "/*";
 
 	/**
 	 * Constructor
@@ -94,20 +96,21 @@ public class RestfulServerExtension implements BeforeEachCallback, AfterEachCall
 	}
 
 	private void startServer() throws Exception {
-		myServer = new Server(0);
+		myServer = new Server(myPort);
 
-		ServletHandler servletHandler = new ServletHandler();
 		myServlet = new RestfulServer(myFhirContext);
 		myServlet.setDefaultPrettyPrint(true);
 		if (myProviders != null) {
 			myServlet.registerProviders(myProviders);
 		}
 		ServletHolder servletHolder = new ServletHolder(myServlet);
-		servletHandler.addServletWithMapping(servletHolder, "/*");
 
 		myConsumers.forEach(t -> t.accept(myServlet));
 
-		myServer.setHandler(servletHandler);
+		ServletContextHandler contextHandler = new ServletContextHandler();
+		contextHandler.addServlet(servletHolder, myServletPath);
+
+		myServer.setHandler(contextHandler);
 		myServer.start();
 		myPort = JettyUtil.getPortForStartedServer(myServer);
 		ourLog.info("Server has started on port {}", myPort);
@@ -175,4 +178,14 @@ public class RestfulServerExtension implements BeforeEachCallback, AfterEachCall
 	public void shutDownServer() throws Exception {
 		JettyUtil.closeServer(myServer);
 	}
+
+	public RestfulServerExtension withServletPath(String theServletPath) {
+		myServletPath = theServletPath;
+		return this;
+	}
+
+	public RestfulServerExtension withPort(int thePort) {
+		myPort = thePort;
+		return this;
+	}
 }
diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml
index 072a4f9a21c..ee6382cca38 100644
--- a/hapi-fhir-testpage-overlay/pom.xml
+++ b/hapi-fhir-testpage-overlay/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-validation-resources-dstu2.1/pom.xml b/hapi-fhir-validation-resources-dstu2.1/pom.xml
index f8cc76070d3..3af7e1b74b1 100644
--- a/hapi-fhir-validation-resources-dstu2.1/pom.xml
+++ b/hapi-fhir-validation-resources-dstu2.1/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-validation-resources-dstu2/pom.xml b/hapi-fhir-validation-resources-dstu2/pom.xml
index a69e2ea236d..53941c421b0 100644
--- a/hapi-fhir-validation-resources-dstu2/pom.xml
+++ b/hapi-fhir-validation-resources-dstu2/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-validation-resources-dstu3/pom.xml b/hapi-fhir-validation-resources-dstu3/pom.xml
index 0061ae4ef6e..9570b03358f 100644
--- a/hapi-fhir-validation-resources-dstu3/pom.xml
+++ b/hapi-fhir-validation-resources-dstu3/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-validation-resources-r4/pom.xml b/hapi-fhir-validation-resources-r4/pom.xml
index ace6fe58362..188381def8a 100644
--- a/hapi-fhir-validation-resources-r4/pom.xml
+++ b/hapi-fhir-validation-resources-r4/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-validation-resources-r5/pom.xml b/hapi-fhir-validation-resources-r5/pom.xml
index 442455129ad..79b31674df1 100644
--- a/hapi-fhir-validation-resources-r5/pom.xml
+++ b/hapi-fhir-validation-resources-r5/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml
index a441397e88e..1d9dcb5397a 100644
--- a/hapi-fhir-validation/pom.xml
+++ b/hapi-fhir-validation/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-deployable-pom</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
 	</parent>
 
diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/RequestValidatingInterceptorDstu3Test.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/RequestValidatingInterceptorDstu3Test.java
index af0ade61f0d..81a3dae5c27 100644
--- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/RequestValidatingInterceptorDstu3Test.java
+++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/RequestValidatingInterceptorDstu3Test.java
@@ -19,6 +19,7 @@ import ca.uhn.fhir.util.TestUtil;
 import ca.uhn.fhir.validation.IValidationContext;
 import ca.uhn.fhir.validation.IValidatorModule;
 import ca.uhn.fhir.validation.ResultSeverityEnum;
+import com.ctc.wstx.shaded.msv_core.verifier.jaxp.DocumentBuilderFactoryImpl;
 import org.apache.commons.io.IOUtils;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.CloseableHttpResponse;
@@ -45,6 +46,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
+import javax.xml.parsers.DocumentBuilderFactory;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ServerCapabilityStatementProviderR4Test.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ServerCapabilityStatementProviderR4Test.java
index f151466e3aa..3f503cdbb8a 100644
--- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ServerCapabilityStatementProviderR4Test.java
+++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ServerCapabilityStatementProviderR4Test.java
@@ -9,6 +9,9 @@ import ca.uhn.fhir.model.primitive.InstantDt;
 import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
 import ca.uhn.fhir.rest.annotation.Create;
 import ca.uhn.fhir.rest.annotation.Delete;
+import ca.uhn.fhir.rest.annotation.GraphQL;
+import ca.uhn.fhir.rest.annotation.GraphQLQueryBody;
+import ca.uhn.fhir.rest.annotation.GraphQLQueryUrl;
 import ca.uhn.fhir.rest.annotation.History;
 import ca.uhn.fhir.rest.annotation.IdParam;
 import ca.uhn.fhir.rest.annotation.IncludeParam;
@@ -23,6 +26,7 @@ import ca.uhn.fhir.rest.annotation.Update;
 import ca.uhn.fhir.rest.annotation.Validate;
 import ca.uhn.fhir.rest.api.Constants;
 import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.RequestTypeEnum;
 import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
 import ca.uhn.fhir.rest.api.server.IBundleProvider;
 import ca.uhn.fhir.rest.api.server.RequestDetails;
@@ -38,14 +42,20 @@ import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
 import ca.uhn.fhir.rest.server.method.IParameter;
 import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
 import ca.uhn.fhir.rest.server.method.SearchParameter;
+import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider;
+import ca.uhn.fhir.rest.server.provider.ProviderConstants;
 import ca.uhn.fhir.rest.server.provider.ServerCapabilityStatementProvider;
 import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
+import ca.uhn.fhir.test.utilities.server.MockServletUtil;
+import ca.uhn.fhir.util.HapiExtensions;
 import ca.uhn.fhir.util.TestUtil;
 import ca.uhn.fhir.validation.FhirValidator;
 import ca.uhn.fhir.validation.ValidationResult;
 import com.google.common.collect.Lists;
 import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
+import org.hl7.fhir.instance.model.api.IBaseParameters;
 import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
 import org.hl7.fhir.instance.model.api.IPrimitiveType;
 import org.hl7.fhir.r4.model.Bundle;
 import org.hl7.fhir.r4.model.CapabilityStatement;
@@ -60,6 +70,7 @@ import org.hl7.fhir.r4.model.DateType;
 import org.hl7.fhir.r4.model.DiagnosticReport;
 import org.hl7.fhir.r4.model.Encounter;
 import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
+import org.hl7.fhir.r4.model.Extension;
 import org.hl7.fhir.r4.model.IdType;
 import org.hl7.fhir.r4.model.Observation;
 import org.hl7.fhir.r4.model.OperationDefinition;
@@ -68,12 +79,10 @@ import org.hl7.fhir.r4.model.OperationDefinition.OperationKind;
 import org.hl7.fhir.r4.model.OperationDefinition.OperationParameterUse;
 import org.hl7.fhir.r4.model.OperationOutcome;
 import org.hl7.fhir.r4.model.Patient;
-import org.hl7.fhir.r4.model.StringType;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import java.util.ArrayList;
@@ -93,6 +102,7 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
@@ -100,1345 +110,1565 @@ import static org.mockito.Mockito.when;
 
 public class ServerCapabilityStatementProviderR4Test {
 
-    public static final String PATIENT_SUB = "PatientSub";
-    public static final String PATIENT_SUB_SUB = "PatientSubSub";
-    public static final String PATIENT_SUB_SUB_2 = "PatientSubSub2";
-    public static final String PATIENT_TRIPLE_SUB = "PatientTripleSub";
-    private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerCapabilityStatementProviderR4Test.class);
-    private final FhirContext myCtx = FhirContext.forCached(FhirVersionEnum.R4);
-    private FhirValidator myValidator;
-
-    @BeforeEach
-    public void before() {
-        myValidator = myCtx.newValidator();
-        myValidator.registerValidatorModule(new FhirInstanceValidator(myCtx));
-    }
-
-    private HttpServletRequest createHttpServletRequest() {
-        HttpServletRequest req = mock(HttpServletRequest.class);
-        when(req.getRequestURI()).thenReturn("/FhirStorm/fhir/Patient/_search");
-        when(req.getServletPath()).thenReturn("/fhir");
-        when(req.getRequestURL()).thenReturn(new StringBuffer().append("http://fhirstorm.dyndns.org:8080/FhirStorm/fhir/Patient/_search"));
-        when(req.getContextPath()).thenReturn("/FhirStorm");
-        return req;
-    }
-
-    private ServletConfig createServletConfig() {
-        ServletConfig sc = mock(ServletConfig.class);
-        when(sc.getServletContext()).thenReturn(null);
-        return sc;
-    }
-
-    private CapabilityStatementRestResourceComponent findRestResource(CapabilityStatement conformance, String wantResource) throws Exception {
-        CapabilityStatementRestResourceComponent resource = null;
-        for (CapabilityStatementRestResourceComponent next : conformance.getRest().get(0).getResource()) {
-            if (next.getType().equals(wantResource)) {
-                resource = next;
-            }
-        }
-        if (resource == null) {
-            throw new Exception("Could not find resource: " + wantResource);
-        }
-        return resource;
-    }
-
-    @Test
-    public void testFormats() throws ServletException {
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new ConditionalProvider());
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement cs = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        List<String> formats = cs
-                .getFormat()
-                .stream()
-                .map(t -> t.getCode())
-                .collect(Collectors.toList());
-        assertThat(formats.toString(), formats, containsInAnyOrder(
-                "application/fhir+xml",
-                "xml",
-                "application/fhir+json",
-                "json",
-                "application/x-turtle",
-                "ttl"
-        ));
-    }
-
-
-    @Test
-    public void testConditionalOperations() throws Exception {
-
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new ConditionalProvider());
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
-        ourLog.info(conf);
-
-        assertEquals(2, conformance.getRest().get(0).getResource().size());
-        CapabilityStatementRestResourceComponent res = conformance.getRest().get(0).getResource().get(1);
-        assertEquals("Patient", res.getType());
-
-        assertTrue(res.getConditionalCreate());
-        assertEquals(ConditionalDeleteStatus.MULTIPLE, res.getConditionalDelete());
-        assertTrue(res.getConditionalUpdate());
-    }
-
-    private RequestDetails createRequestDetails(RestfulServer theServer) {
-        ServletRequestDetails retVal = new ServletRequestDetails(null);
-        retVal.setServer(theServer);
-        retVal.setFhirServerBase("http://localhost/baseR4");
-        return retVal;
-    }
-
-    @Test
-    public void testExtendedOperationReturningBundle() throws Exception {
-
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new ProviderWithExtendedOperationReturningBundle());
-        rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        validate(conformance);
-
-        assertEquals(1, conformance.getRest().get(0).getOperation().size());
-        assertEquals("everything", conformance.getRest().get(0).getOperation().get(0).getName());
-
-        OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
-        validate(opDef);
-        assertEquals("everything", opDef.getCode());
-        assertThat(opDef.getSystem(), is(false));
-        assertThat(opDef.getType(), is(false));
-        assertThat(opDef.getInstance(), is(true));
-    }
-
-    @Test
-    public void testExtendedOperationReturningBundleOperation() throws Exception {
+	public static final String PATIENT_SUB = "PatientSub";
+	public static final String PATIENT_SUB_SUB = "PatientSubSub";
+	public static final String PATIENT_SUB_SUB_2 = "PatientSubSub2";
+	public static final String PATIENT_TRIPLE_SUB = "PatientTripleSub";
+	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerCapabilityStatementProviderR4Test.class);
+	private final FhirContext myCtx = FhirContext.forCached(FhirVersionEnum.R4);
+	private FhirValidator myValidator;
+
+	@BeforeEach
+	public void before() {
+		myValidator = myCtx.newValidator();
+		myValidator.registerValidatorModule(new FhirInstanceValidator(myCtx));
+	}
+
+	private HttpServletRequest createHttpServletRequest() {
+		HttpServletRequest req = mock(HttpServletRequest.class);
+		when(req.getRequestURI()).thenReturn("/FhirStorm/fhir/Patient/_search");
+		when(req.getServletPath()).thenReturn("/fhir");
+		when(req.getRequestURL()).thenReturn(new StringBuffer().append("http://fhirstorm.dyndns.org:8080/FhirStorm/fhir/Patient/_search"));
+		when(req.getContextPath()).thenReturn("/FhirStorm");
+		return req;
+	}
+
+	private CapabilityStatementRestResourceComponent findRestResource(CapabilityStatement conformance, String wantResource) throws Exception {
+		CapabilityStatementRestResourceComponent resource = null;
+		for (CapabilityStatementRestResourceComponent next : conformance.getRest().get(0).getResource()) {
+			if (next.getType().equals(wantResource)) {
+				resource = next;
+			}
+		}
+		if (resource == null) {
+			throw new Exception("Could not find resource: " + wantResource);
+		}
+		return resource;
+	}
+
+	@Test
+	public void testFormats() throws ServletException {
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new ConditionalProvider());
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement cs = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		List<String> formats = cs
+			.getFormat()
+			.stream()
+			.map(t -> t.getCode())
+			.collect(Collectors.toList());
+		assertThat(formats.toString(), formats, containsInAnyOrder(
+			"application/fhir+xml",
+			"xml",
+			"application/fhir+json",
+			"json",
+			"application/x-turtle",
+			"ttl"
+		));
+	}
+
+
+	@Test
+	public void testConditionalOperations() throws Exception {
+
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new ConditionalProvider());
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
+		ourLog.info(conf);
+
+		assertEquals(2, conformance.getRest().get(0).getResource().size());
+		CapabilityStatementRestResourceComponent res = conformance.getRest().get(0).getResource().get(1);
+		assertEquals("Patient", res.getType());
+
+		assertTrue(res.getConditionalCreate());
+		assertEquals(ConditionalDeleteStatus.MULTIPLE, res.getConditionalDelete());
+		assertTrue(res.getConditionalUpdate());
+	}
+
+	private RequestDetails createRequestDetails(RestfulServer theServer) {
+		ServletRequestDetails retVal = new ServletRequestDetails();
+		retVal.setServer(theServer);
+		retVal.setFhirServerBase("http://localhost/baseR4");
+		return retVal;
+	}
+
+	@Test
+	public void testExtendedOperationReturningBundle() throws Exception {
+
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new ProviderWithExtendedOperationReturningBundle());
+		rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		validate(conformance);
+
+		CapabilityStatementRestResourceComponent patient = conformance.getRestFirstRep().getResource().stream().filter(t -> t.getType().equals("Patient")).findFirst().orElseThrow(() -> new IllegalArgumentException());
+		assertEquals(1, patient.getOperation().size());
+		assertEquals("everything", patient.getOperation().get(0).getName());
+		assertEquals("http://localhost/baseR4/OperationDefinition/Patient-i-everything", patient.getOperation().get(0).getDefinition());
+
+		OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
+		validate(opDef);
+		assertEquals("everything", opDef.getCode());
+		assertThat(opDef.getSystem(), is(false));
+		assertThat(opDef.getType(), is(false));
+		assertThat(opDef.getInstance(), is(true));
+	}
 
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new ProviderWithExtendedOperationReturningBundle());
+	@Test
+	public void testExtendedOperationReturningBundleOperation() throws Exception {
 
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
-        };
-        rs.setServerConformanceProvider(sc);
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new ProviderWithExtendedOperationReturningBundle());
+		rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
 
-        rs.init(createServletConfig());
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
+		};
+		rs.setServerConformanceProvider(sc);
 
-        OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
-        validate(opDef);
+		rs.init(MockServletUtil.createServletConfig());
 
-        String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef);
-        ourLog.info(conf);
+		OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
+		validate(opDef);
 
-        assertEquals("everything", opDef.getCode());
-        assertEquals(false, opDef.getAffectsState());
-    }
+		String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef);
+		ourLog.info(conf);
 
-    @Test
-    public void testInstanceHistorySupported() throws Exception {
+		assertEquals("everything", opDef.getCode());
+		assertEquals(false, opDef.getAffectsState());
+	}
 
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new InstanceHistoryProvider());
+	@Test
+	public void testInstanceHistorySupported() throws Exception {
 
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new InstanceHistoryProvider());
 
-        rs.init(createServletConfig());
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
 
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        String conf = validate(conformance);
+		rs.init(MockServletUtil.createServletConfig());
 
-        conf = myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
-        assertThat(conf, containsString("<interaction><code value=\"" + TypeRestfulInteraction.HISTORYINSTANCE.toCode() + "\"/></interaction>"));
-    }
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		String conf = validate(conformance);
 
-    @Test
-    public void testMultiOptionalDocumentation() throws Exception {
+		conf = myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
+		assertThat(conf, containsString("<interaction><code value=\"" + TypeRestfulInteraction.HISTORYINSTANCE.toCode() + "\"/></interaction>"));
+	}
 
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new MultiOptionalProvider());
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        boolean found = false;
-        Collection<ResourceBinding> resourceBindings = rs.getResourceBindings();
-        for (ResourceBinding resourceBinding : resourceBindings) {
-            if (resourceBinding.getResourceName().equals("Patient")) {
-                List<BaseMethodBinding<?>> methodBindings = resourceBinding.getMethodBindings();
-                SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
-                SearchParameter param = (SearchParameter) binding.getParameters().iterator().next();
-                assertEquals("The patient's identifier", param.getDescription());
-                found = true;
-            }
-        }
-
-        assertTrue(found);
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        String conf = validate(conformance);
-
-        assertThat(conf, containsString("<documentation value=\"The patient's identifier\"/>"));
-        assertThat(conf, containsString("<documentation value=\"The patient's name\"/>"));
-        assertThat(conf, containsString("<type value=\"token\"/>"));
-    }
-
-    @Test
-    public void testNonConditionalOperations() throws Exception {
-
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new NonConditionalProvider());
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        validate(conformance);
-
-        CapabilityStatementRestResourceComponent res = conformance.getRest().get(0).getResource().get(1);
-        assertEquals("Patient", res.getType());
-
-        assertNull(res.getConditionalCreateElement().getValue());
-        assertNull(res.getConditionalDeleteElement().getValue());
-        assertNull(res.getConditionalUpdateElement().getValue());
-    }
-
-    /**
-     * See #379
-     */
-    @Test
-    public void testOperationAcrossMultipleTypes() throws Exception {
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new MultiTypePatientProvider(), new MultiTypeEncounterProvider());
-        rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-
-        validate(conformance);
-
-        assertEquals(4, conformance.getRest().get(0).getOperation().size());
-        List<String> operationNames = toOperationNames(conformance.getRest().get(0).getOperation());
-        assertThat(operationNames, containsInAnyOrder("someOp", "validate", "someOp", "validate"));
-
-        List<String> operationIdParts = toOperationIdParts(conformance.getRest().get(0).getOperation());
-        assertThat(operationIdParts, containsInAnyOrder("Patient-i-someOp", "Encounter-i-someOp", "Patient-i-validate", "Encounter-i-validate"));
-
-        {
-            OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-someOp"), createRequestDetails(rs));
-            validate(opDef);
-            ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
-            Set<String> types = toStrings(opDef.getResource());
-            assertEquals("someOp", opDef.getCode());
-            assertEquals(true, opDef.getInstance());
-            assertEquals(false, opDef.getSystem());
-            assertThat(types, containsInAnyOrder("Patient"));
-            assertEquals(2, opDef.getParameter().size());
-            assertEquals("someOpParam1", opDef.getParameter().get(0).getName());
-            assertEquals("date", opDef.getParameter().get(0).getType());
-            assertEquals("someOpParam2", opDef.getParameter().get(1).getName());
-            assertEquals("Patient", opDef.getParameter().get(1).getType());
-        }
-        {
-            OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Encounter-i-someOp"), createRequestDetails(rs));
-            validate(opDef);
-            ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
-            Set<String> types = toStrings(opDef.getResource());
-            assertEquals("someOp", opDef.getCode());
-            assertEquals(true, opDef.getInstance());
-            assertEquals(false, opDef.getSystem());
-            assertThat(types, containsInAnyOrder("Encounter"));
-            assertEquals(2, opDef.getParameter().size());
-            assertEquals("someOpParam1", opDef.getParameter().get(0).getName());
-            assertEquals("date", opDef.getParameter().get(0).getType());
-            assertEquals("someOpParam2", opDef.getParameter().get(1).getName());
-            assertEquals("Encounter", opDef.getParameter().get(1).getType());
-        }
-        {
-            OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-validate"), createRequestDetails(rs));
-            validate(opDef);
-            ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
-            Set<String> types = toStrings(opDef.getResource());
-            assertEquals("validate", opDef.getCode());
-            assertEquals(true, opDef.getInstance());
-            assertEquals(false, opDef.getSystem());
-            assertThat(types, containsInAnyOrder("Patient"));
-            assertEquals(1, opDef.getParameter().size());
-            assertEquals("resource", opDef.getParameter().get(0).getName());
-            assertEquals("Patient", opDef.getParameter().get(0).getType());
-        }
-    }
-
-    @Test
-    public void testOperationDocumentation() throws Exception {
-
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new SearchProvider());
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-
-        String conf = validate(conformance);
-
-        assertThat(conf, containsString("<documentation value=\"The patient's identifier (MRN or other card number)\"/>"));
-        assertThat(conf, containsString("<type value=\"token\"/>"));
-
-    }
-
-    @Test
-    public void testOperationOnNoTypes() throws Exception {
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new PlainProviderWithExtendedOperationOnNoType());
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
-            @Override
-            public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
-                return (CapabilityStatement) super.getServerConformance(theRequest, createRequestDetails(rs));
-            }
-        };
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/-is-plain"), createRequestDetails(rs));
-        validate(opDef);
-
-        assertEquals("plain", opDef.getCode());
-        assertEquals(false, opDef.getAffectsState());
-        assertEquals(3, opDef.getParameter().size());
-
-        assertTrue(opDef.getParameter().get(0).hasName());
-        assertEquals("start", opDef.getParameter().get(0).getName());
-        assertEquals("in", opDef.getParameter().get(0).getUse().toCode());
-        assertEquals("0", opDef.getParameter().get(0).getMinElement().getValueAsString());
-        assertEquals("date", opDef.getParameter().get(0).getTypeElement().getValueAsString());
-
-        assertEquals("out1", opDef.getParameter().get(2).getName());
-        assertEquals("out", opDef.getParameter().get(2).getUse().toCode());
-        assertEquals("1", opDef.getParameter().get(2).getMinElement().getValueAsString());
-        assertEquals("2", opDef.getParameter().get(2).getMaxElement().getValueAsString());
-        assertEquals("string", opDef.getParameter().get(2).getTypeElement().getValueAsString());
-
-        assertThat(opDef.getSystem(), is(true));
-        assertThat(opDef.getType(), is(false));
-        assertThat(opDef.getInstance(), is(true));
-    }
-
-    @Test
-    public void testProviderWithRequiredAndOptional() throws Exception {
-
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new ProviderWithRequiredAndOptional());
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        validate(conformance);
-
-        CapabilityStatementRestComponent rest = conformance.getRest().get(0);
-        CapabilityStatementRestResourceComponent res = rest.getResource().get(0);
-        assertEquals("DiagnosticReport", res.getType());
-
-        assertEquals("subject.identifier", res.getSearchParam().get(0).getName());
+	@Test
+	public void testMultiOptionalDocumentation() throws Exception {
+
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new MultiOptionalProvider());
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		boolean found = false;
+		Collection<ResourceBinding> resourceBindings = rs.getResourceBindings();
+		for (ResourceBinding resourceBinding : resourceBindings) {
+			if (resourceBinding.getResourceName().equals("Patient")) {
+				List<BaseMethodBinding<?>> methodBindings = resourceBinding.getMethodBindings();
+				SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
+				SearchParameter param = (SearchParameter) binding.getParameters().iterator().next();
+				assertEquals("The patient's identifier", param.getDescription());
+				found = true;
+			}
+		}
+
+		assertTrue(found);
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		String conf = validate(conformance);
+
+		assertThat(conf, containsString("<documentation value=\"The patient's identifier\"/>"));
+		assertThat(conf, containsString("<documentation value=\"The patient's name\"/>"));
+		assertThat(conf, containsString("<type value=\"token\"/>"));
+	}
+
+	@Test
+	public void testNonConditionalOperations() throws Exception {
+
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new NonConditionalProvider());
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		validate(conformance);
+
+		CapabilityStatementRestResourceComponent res = conformance.getRest().get(0).getResource().get(1);
+		assertEquals("Patient", res.getType());
+
+		assertNull(res.getConditionalCreateElement().getValue());
+		assertNull(res.getConditionalDeleteElement().getValue());
+		assertNull(res.getConditionalUpdateElement().getValue());
+	}
+
+
+	@Test
+	public void testOperationParameterDocumentation() throws Exception {
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new MultiTypePatientProvider(), new MultiTypeEncounterProvider());
+		rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		validate(conformance);
+
+		IdType operationId = new IdType("OperationDefinition/EncounterPatient-i-someOp");
+		RequestDetails requestDetails = createRequestDetails(rs);
+		OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(operationId, requestDetails);
+		validate(opDef);
+		Set<String> types = toStrings(opDef.getResource());
+		assertEquals("someOp", opDef.getCode());
+		assertEquals(true, opDef.getInstance());
+		assertEquals(false, opDef.getSystem());
+		assertThat(types, containsInAnyOrder("Patient", "Encounter"));
+		assertEquals(2, opDef.getParameter().size());
+		assertEquals("someOpParam1", opDef.getParameter().get(0).getName());
+		assertEquals("date", opDef.getParameter().get(0).getType());
+		assertEquals("Start description", opDef.getParameter().get(0).getDocumentation());
+
+		List<Extension> exampleExtensions = opDef.getParameter().get(0).getExtensionsByUrl(HapiExtensions.EXT_OP_PARAMETER_EXAMPLE_VALUE);
+		assertEquals(2, exampleExtensions.size());
+		assertEquals("2001", exampleExtensions.get(0).getValueAsPrimitive().getValueAsString());
+		assertEquals("2002", exampleExtensions.get(1).getValueAsPrimitive().getValueAsString());
+	}
+
+
+	/**
+	 * See #379
+	 */
+	@Test
+	public void testOperationAcrossMultipleTypes() throws Exception {
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new MultiTypePatientProvider(), new MultiTypeEncounterProvider());
+		rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+		validate(conformance);
+
+		CapabilityStatementRestResourceComponent patient = conformance.getRestFirstRep().getResource().stream().filter(t -> t.getType().equals("Patient")).findFirst().orElseThrow(() -> new IllegalArgumentException());
+		assertEquals(2, patient.getOperation().size());
+		assertThat(toOperationNames(patient.getOperation()), containsInAnyOrder("someOp", "validate"));
+		assertThat(toOperationDefinitions(patient.getOperation()), containsInAnyOrder("http://localhost/baseR4/OperationDefinition/EncounterPatient-i-someOp", "http://localhost/baseR4/OperationDefinition/EncounterPatient-i-validate"));
+
+		CapabilityStatementRestResourceComponent encounter = conformance.getRestFirstRep().getResource().stream().filter(t -> t.getType().equals("Encounter")).findFirst().orElseThrow(() -> new IllegalArgumentException());
+		assertEquals(2, encounter.getOperation().size());
+		assertThat(toOperationNames(encounter.getOperation()), containsInAnyOrder("someOp", "validate"));
+		assertThat(toOperationDefinitions(encounter.getOperation()).toString(), toOperationDefinitions(encounter.getOperation()), containsInAnyOrder("http://localhost/baseR4/OperationDefinition/EncounterPatient-i-someOp", "http://localhost/baseR4/OperationDefinition/EncounterPatient-i-validate"));
+
+
+		{
+			OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/EncounterPatient-i-someOp"), createRequestDetails(rs));
+			validate(opDef);
+			ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
+			Set<String> types = toStrings(opDef.getResource());
+			assertEquals("someOp", opDef.getCode());
+			assertEquals(true, opDef.getInstance());
+			assertEquals(false, opDef.getSystem());
+			assertThat(types, containsInAnyOrder("Patient", "Encounter"));
+			assertEquals(2, opDef.getParameter().size());
+			assertEquals("someOpParam1", opDef.getParameter().get(0).getName());
+			assertEquals("date", opDef.getParameter().get(0).getType());
+			assertEquals("someOpParam2", opDef.getParameter().get(1).getName());
+			assertEquals("Resource", opDef.getParameter().get(1).getType());
+		}
+		{
+			OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType("OperationDefinition/EncounterPatient-i-validate"), createRequestDetails(rs));
+			validate(opDef);
+			ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef));
+			Set<String> types = toStrings(opDef.getResource());
+			assertEquals("validate", opDef.getCode());
+			assertEquals(true, opDef.getInstance());
+			assertEquals(false, opDef.getSystem());
+			assertThat(types, containsInAnyOrder("Patient", "Encounter"));
+			assertEquals(1, opDef.getParameter().size());
+			assertEquals("resource", opDef.getParameter().get(0).getName());
+			assertEquals("Resource", opDef.getParameter().get(0).getType());
+		}
+	}
+
+	@Test
+	public void testOperationDocumentation() throws Exception {
+
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new SearchProvider());
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+		String conf = validate(conformance);
+
+		assertThat(conf, containsString("<documentation value=\"The patient's identifier (MRN or other card number)\"/>"));
+		assertThat(conf, containsString("<type value=\"token\"/>"));
+
+	}
+
+	@Test
+	public void testProviderWithRequiredAndOptional() throws Exception {
+
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new ProviderWithRequiredAndOptional());
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		validate(conformance);
+
+		CapabilityStatementRestComponent rest = conformance.getRest().get(0);
+		CapabilityStatementRestResourceComponent res = rest.getResource().get(0);
+		assertEquals("DiagnosticReport", res.getType());
+
+		assertEquals("subject.identifier", res.getSearchParam().get(0).getName());
 //		assertEquals("identifier", res.getSearchParam().get(0).getChain().get(0).getValue());
 
-        assertEquals(DiagnosticReport.SP_CODE, res.getSearchParam().get(1).getName());
+		assertEquals(DiagnosticReport.SP_CODE, res.getSearchParam().get(1).getName());
 
-        assertEquals(DiagnosticReport.SP_DATE, res.getSearchParam().get(2).getName());
+		assertEquals(DiagnosticReport.SP_DATE, res.getSearchParam().get(2).getName());
 
-        assertEquals(1, res.getSearchInclude().size());
-        assertEquals("DiagnosticReport.result", res.getSearchInclude().get(0).getValue());
-    }
+		assertEquals(1, res.getSearchInclude().size());
+		assertEquals("DiagnosticReport.result", res.getSearchInclude().get(0).getValue());
+	}
 
-    @Test
-    public void testReadAndVReadSupported() throws Exception {
+	@Test
+	public void testReadAndVReadSupported() throws Exception {
 
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new VreadProvider());
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new VreadProvider());
 
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
 
-        rs.init(createServletConfig());
+		rs.init(MockServletUtil.createServletConfig());
 
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        String conf = validate(conformance);
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		String conf = validate(conformance);
 
-        assertThat(conf, containsString("<interaction><code value=\"vread\"/></interaction>"));
-        assertThat(conf, containsString("<interaction><code value=\"read\"/></interaction>"));
-    }
+		assertThat(conf, containsString("<interaction><code value=\"vread\"/></interaction>"));
+		assertThat(conf, containsString("<interaction><code value=\"read\"/></interaction>"));
+	}
 
-    @Test
-    public void testReadSupported() throws Exception {
+	@Test
+	public void testReadSupported() throws Exception {
 
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new ReadProvider());
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new ReadProvider());
 
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
 
-        rs.init(createServletConfig());
+		rs.init(MockServletUtil.createServletConfig());
 
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
-        ourLog.info(conf);
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
+		ourLog.info(conf);
 
-        conf = myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
-        assertThat(conf, not(containsString("<interaction><code value=\"vread\"/></interaction>")));
-        assertThat(conf, containsString("<interaction><code value=\"read\"/></interaction>"));
-    }
+		conf = myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance);
+		assertThat(conf, not(containsString("<interaction><code value=\"vread\"/></interaction>")));
+		assertThat(conf, containsString("<interaction><code value=\"read\"/></interaction>"));
+	}
 
-    @Test
-    public void testSearchParameterDocumentation() throws Exception {
+	@Test
+	public void testSearchParameterDocumentation() throws Exception {
 
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new SearchProvider());
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new SearchProvider());
 
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
 
-        rs.init(createServletConfig());
+		rs.init(MockServletUtil.createServletConfig());
 
-        boolean found = false;
-        Collection<ResourceBinding> resourceBindings = rs.getResourceBindings();
-        for (ResourceBinding resourceBinding : resourceBindings) {
-            if (resourceBinding.getResourceName().equals("Patient")) {
-                List<BaseMethodBinding<?>> methodBindings = resourceBinding.getMethodBindings();
-                SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
-                for (IParameter next : binding.getParameters()) {
-                    SearchParameter param = (SearchParameter) next;
-                    if (param.getDescription().contains("The patient's identifier (MRN or other card number")) {
-                        found = true;
-                    }
-                }
-                found = true;
-            }
-        }
-        assertTrue(found);
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		boolean found = false;
+		Collection<ResourceBinding> resourceBindings = rs.getResourceBindings();
+		for (ResourceBinding resourceBinding : resourceBindings) {
+			if (resourceBinding.getResourceName().equals("Patient")) {
+				List<BaseMethodBinding<?>> methodBindings = resourceBinding.getMethodBindings();
+				SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
+				for (IParameter next : binding.getParameters()) {
+					SearchParameter param = (SearchParameter) next;
+					if (param.getDescription().contains("The patient's identifier (MRN or other card number")) {
+						found = true;
+					}
+				}
+				found = true;
+			}
+		}
+		assertTrue(found);
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
 
-        String conf = validate(conformance);
+		String conf = validate(conformance);
 
-        assertThat(conf, containsString("<documentation value=\"The patient's identifier (MRN or other card number)\"/>"));
-        assertThat(conf, containsString("<type value=\"token\"/>"));
+		assertThat(conf, containsString("<documentation value=\"The patient's identifier (MRN or other card number)\"/>"));
+		assertThat(conf, containsString("<type value=\"token\"/>"));
 
-    }
+	}
 
 
-    @Test
-    public void testFormatIncludesSpecialNonMediaTypeFormats() throws ServletException {
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new SearchProvider());
+	@Test
+	public void testFormatIncludesSpecialNonMediaTypeFormats() throws ServletException {
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new SearchProvider());
 
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
 
-        rs.init(createServletConfig());
-        CapabilityStatement serverConformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		rs.init(MockServletUtil.createServletConfig());
+		CapabilityStatement serverConformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
 
-        List<String> formatCodes = serverConformance.getFormat().stream().map(c -> c.getCode()).collect(Collectors.toList());
+		List<String> formatCodes = serverConformance.getFormat().stream().map(c -> c.getCode()).collect(Collectors.toList());
 
-        assertThat(formatCodes, hasItem(Constants.FORMAT_XML));
-        assertThat(formatCodes, hasItem(Constants.FORMAT_JSON));
-        assertThat(formatCodes, hasItem(Constants.CT_FHIR_JSON_NEW));
-        assertThat(formatCodes, hasItem(Constants.CT_FHIR_XML_NEW));
-    }
+		assertThat(formatCodes, hasItem(Constants.FORMAT_XML));
+		assertThat(formatCodes, hasItem(Constants.FORMAT_JSON));
+		assertThat(formatCodes, hasItem(Constants.CT_FHIR_JSON_NEW));
+		assertThat(formatCodes, hasItem(Constants.CT_FHIR_XML_NEW));
+	}
 
-    /**
-     * See #286
-     */
-    @Test
-    public void testSearchReferenceParameterDocumentation() throws Exception {
+	/**
+	 * See #286
+	 */
+	@Test
+	public void testSearchReferenceParameterDocumentation() throws Exception {
 
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new PatientResourceProvider());
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new PatientResourceProvider());
 
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
 
-        rs.init(createServletConfig());
+		rs.init(MockServletUtil.createServletConfig());
 
-        boolean found = false;
-        Collection<ResourceBinding> resourceBindings = rs.getResourceBindings();
-        for (ResourceBinding resourceBinding : resourceBindings) {
-            if (resourceBinding.getResourceName().equals("Patient")) {
-                List<BaseMethodBinding<?>> methodBindings = resourceBinding.getMethodBindings();
-                SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
-                SearchParameter param = (SearchParameter) binding.getParameters().get(25);
-                assertEquals("The organization at which this person is a patient", param.getDescription());
-                found = true;
-            }
-        }
-        assertTrue(found);
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		boolean found = false;
+		Collection<ResourceBinding> resourceBindings = rs.getResourceBindings();
+		for (ResourceBinding resourceBinding : resourceBindings) {
+			if (resourceBinding.getResourceName().equals("Patient")) {
+				List<BaseMethodBinding<?>> methodBindings = resourceBinding.getMethodBindings();
+				SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
+				SearchParameter param = (SearchParameter) binding.getParameters().get(25);
+				assertEquals("The organization at which this person is a patient", param.getDescription());
+				found = true;
+			}
+		}
+		assertTrue(found);
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
 
-        String conf = validate(conformance);
+		String conf = validate(conformance);
 
-    }
+	}
 
-    /**
-     * See #286
-     */
-    @Test
-    public void testSearchReferenceParameterWithWhitelistDocumentation() throws Exception {
+	/**
+	 * See #286
+	 */
+	@Test
+	public void testSearchReferenceParameterWithWhitelistDocumentation() throws Exception {
 
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new SearchProviderWithWhitelist());
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new SearchProviderWithWhitelist());
 
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
 
-        rs.init(createServletConfig());
+		rs.init(MockServletUtil.createServletConfig());
 
-        boolean found = false;
-        Collection<ResourceBinding> resourceBindings = rs.getResourceBindings();
-        for (ResourceBinding resourceBinding : resourceBindings) {
-            if (resourceBinding.getResourceName().equals("Patient")) {
-                List<BaseMethodBinding<?>> methodBindings = resourceBinding.getMethodBindings();
-                SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
-                SearchParameter param = (SearchParameter) binding.getParameters().get(0);
-                assertEquals("The organization at which this person is a patient", param.getDescription());
-                found = true;
-            }
-        }
-        assertTrue(found);
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		boolean found = false;
+		Collection<ResourceBinding> resourceBindings = rs.getResourceBindings();
+		for (ResourceBinding resourceBinding : resourceBindings) {
+			if (resourceBinding.getResourceName().equals("Patient")) {
+				List<BaseMethodBinding<?>> methodBindings = resourceBinding.getMethodBindings();
+				SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
+				SearchParameter param = (SearchParameter) binding.getParameters().get(0);
+				assertEquals("The organization at which this person is a patient", param.getDescription());
+				found = true;
+			}
+		}
+		assertTrue(found);
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
 
-        String conf = validate(conformance);
+		String conf = validate(conformance);
 
-        CapabilityStatementRestResourceComponent resource = findRestResource(conformance, "Patient");
+		CapabilityStatementRestResourceComponent resource = findRestResource(conformance, "Patient");
 
-        CapabilityStatementRestResourceSearchParamComponent param = resource.getSearchParam().get(0);
+		CapabilityStatementRestResourceSearchParamComponent param = resource.getSearchParam().get(0);
 //		assertEquals("bar", param.getChain().get(0).getValue());
 //		assertEquals("foo", param.getChain().get(1).getValue());
 //		assertEquals(2, param.getChain().size());
-    }
+	}
 
-    @Test
-    public void testSearchReferenceParameterWithList() throws Exception {
+	@Test
+	public void testSearchReferenceParameterWithList() throws Exception {
 
-        RestfulServer rsNoType = new RestfulServer(myCtx) {
-            @Override
-            public RestfulServerConfiguration createConfiguration() {
-                RestfulServerConfiguration retVal = super.createConfiguration();
-                retVal.setConformanceDate(new InstantDt("2011-02-22T11:22:33Z"));
-                return retVal;
-            }
-        };
-        rsNoType.registerProvider(new SearchProviderWithListNoType());
-        ServerCapabilityStatementProvider scNoType = new ServerCapabilityStatementProvider(rsNoType);
-        rsNoType.setServerConformanceProvider(scNoType);
-        rsNoType.init(createServletConfig());
+		RestfulServer rsNoType = new RestfulServer(myCtx) {
+			@Override
+			public RestfulServerConfiguration createConfiguration() {
+				RestfulServerConfiguration retVal = super.createConfiguration();
+				retVal.setConformanceDate(new InstantDt("2011-02-22T11:22:33Z"));
+				return retVal;
+			}
+		};
+		rsNoType.registerProvider(new SearchProviderWithListNoType());
+		ServerCapabilityStatementProvider scNoType = new ServerCapabilityStatementProvider(rsNoType);
+		rsNoType.setServerConformanceProvider(scNoType);
+		rsNoType.init(MockServletUtil.createServletConfig());
 
-        CapabilityStatement conformance = (CapabilityStatement) scNoType.getServerConformance(createHttpServletRequest(), createRequestDetails(rsNoType));
-        conformance.setId("");
-        String confNoType = validate(conformance);
+		CapabilityStatement conformance = (CapabilityStatement) scNoType.getServerConformance(createHttpServletRequest(), createRequestDetails(rsNoType));
+		conformance.setId("");
+		String confNoType = validate(conformance);
 
-        RestfulServer rsWithType = new RestfulServer(myCtx) {
-            @Override
-            public RestfulServerConfiguration createConfiguration() {
-                RestfulServerConfiguration retVal = super.createConfiguration();
-                retVal.setConformanceDate(new InstantDt("2011-02-22T11:22:33Z"));
-                return retVal;
-            }
-        };
-        rsWithType.registerProvider(new SearchProviderWithListWithType());
-        ServerCapabilityStatementProvider scWithType = new ServerCapabilityStatementProvider(rsWithType);
-        rsWithType.setServerConformanceProvider(scWithType);
-        rsWithType.init(createServletConfig());
+		RestfulServer rsWithType = new RestfulServer(myCtx) {
+			@Override
+			public RestfulServerConfiguration createConfiguration() {
+				RestfulServerConfiguration retVal = super.createConfiguration();
+				retVal.setConformanceDate(new InstantDt("2011-02-22T11:22:33Z"));
+				return retVal;
+			}
+		};
+		rsWithType.registerProvider(new SearchProviderWithListWithType());
+		ServerCapabilityStatementProvider scWithType = new ServerCapabilityStatementProvider(rsWithType);
+		rsWithType.setServerConformanceProvider(scWithType);
+		rsWithType.init(MockServletUtil.createServletConfig());
 
-        CapabilityStatement conformanceWithType = (CapabilityStatement) scWithType.getServerConformance(createHttpServletRequest(), createRequestDetails(rsWithType));
-        conformanceWithType.setId("");
-        String confWithType = validate(conformanceWithType);
+		CapabilityStatement conformanceWithType = (CapabilityStatement) scWithType.getServerConformance(createHttpServletRequest(), createRequestDetails(rsWithType));
+		conformanceWithType.setId("");
+		String confWithType = validate(conformanceWithType);
 
-        assertEquals(confNoType, confWithType);
-        assertThat(confNoType, containsString("<date value=\"2011-02-22T11:22:33Z\"/>"));
-    }
+		assertEquals(confNoType, confWithType);
+		assertThat(confNoType, containsString("<date value=\"2011-02-22T11:22:33Z\"/>"));
+	}
 
-    @Test
-    public void testSystemHistorySupported() throws Exception {
+	@Test
+	public void testSystemHistorySupported() throws Exception {
 
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new SystemHistoryProvider());
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new SystemHistoryProvider());
 
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
 
-        rs.init(createServletConfig());
+		rs.init(MockServletUtil.createServletConfig());
 
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        String conf = validate(conformance);
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		String conf = validate(conformance);
 
-        assertThat(conf, containsString("<interaction><code value=\"" + SystemRestfulInteraction.HISTORYSYSTEM.toCode() + "\"/></interaction>"));
-    }
+		assertThat(conf, containsString("<interaction><code value=\"" + SystemRestfulInteraction.HISTORYSYSTEM.toCode() + "\"/></interaction>"));
+	}
 
-    @Test
-    public void testTypeHistorySupported() throws Exception {
+	@Test
+	public void testTypeHistorySupported() throws Exception {
 
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new TypeHistoryProvider());
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new TypeHistoryProvider());
 
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
 
-        rs.init(createServletConfig());
+		rs.init(MockServletUtil.createServletConfig());
 
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        String conf = validate(conformance);
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		String conf = validate(conformance);
 
-        assertThat(conf, containsString("<interaction><code value=\"" + TypeRestfulInteraction.HISTORYTYPE.toCode() + "\"/></interaction>"));
-    }
+		assertThat(conf, containsString("<interaction><code value=\"" + TypeRestfulInteraction.HISTORYTYPE.toCode() + "\"/></interaction>"));
+	}
+
+	@Test
+	public void testStaticIncludeChains() throws Exception {
+
+		class MyProvider implements IResourceProvider {
+
+			@Override
+			public Class<DiagnosticReport> getResourceType() {
+				return DiagnosticReport.class;
+			}
+
+			@Search
+			public List<IBaseResource> search(@RequiredParam(name = DiagnosticReport.SP_PATIENT + "." + Patient.SP_FAMILY) StringParam lastName,
+														 @RequiredParam(name = DiagnosticReport.SP_PATIENT + "." + Patient.SP_GIVEN) StringParam firstName,
+														 @RequiredParam(name = DiagnosticReport.SP_PATIENT + "." + Patient.SP_BIRTHDATE) DateParam dob,
+														 @OptionalParam(name = DiagnosticReport.SP_DATE) DateRangeParam range) {
+				return null;
+			}
+
+		}
+
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new MyProvider());
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
+		};
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement opDef = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+		validate(opDef);
+
+		CapabilityStatementRestResourceComponent resource = opDef.getRest().get(0).getResource().get(0);
+		assertEquals("DiagnosticReport", resource.getType());
+		List<String> searchParamNames = resource.getSearchParam().stream().map(t -> t.getName()).collect(Collectors.toList());
+		assertThat(searchParamNames, containsInAnyOrder("patient.birthdate", "patient.family", "patient.given", "date"));
+	}
+
+	@Test
+	public void testGraphQLOperation() throws Exception {
+
+		class MyProvider {
+
+			@Description(value = "This operation invokes a GraphQL expression for fetching an joining a graph of resources, returning them in a custom format.")
+			@GraphQL(type = RequestTypeEnum.GET)
+			public String processGraphQlGetRequest(ServletRequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQueryUrl String queryUrl) {
+				throw new IllegalStateException();
+			}
+
+			@Description(value = "This operation invokes a GraphQL expression for fetching an joining a graph of resources, returning them in a custom format.")
+			@GraphQL(type = RequestTypeEnum.POST)
+			public String processGraphQlPostRequest(ServletRequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQueryBody String queryBody) {
+				throw new IllegalStateException();
+			}
 
-    @Test
-    public void testStaticIncludeChains() throws Exception {
+		}
 
-        class MyProvider implements IResourceProvider {
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
+		rs.registerProvider(new MyProvider());
+		rs.registerProvider(new HashMapResourceProvider<>(myCtx, Patient.class));
+		rs.registerProvider(new HashMapResourceProvider<>(myCtx, Observation.class));
 
-            @Override
-            public Class<DiagnosticReport> getResourceType() {
-                return DiagnosticReport.class;
-            }
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
+		};
+		rs.setServerConformanceProvider(sc);
 
-            @Search
-            public List<IBaseResource> search(@RequiredParam(name = DiagnosticReport.SP_PATIENT + "." + Patient.SP_FAMILY) StringParam lastName,
-                                              @RequiredParam(name = DiagnosticReport.SP_PATIENT + "." + Patient.SP_GIVEN) StringParam firstName,
-                                              @RequiredParam(name = DiagnosticReport.SP_PATIENT + "." + Patient.SP_BIRTHDATE) DateParam dob,
-                                              @OptionalParam(name = DiagnosticReport.SP_DATE) DateRangeParam range) {
-                return null;
-            }
+		rs.init(MockServletUtil.createServletConfig());
 
-        }
-
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new MyProvider());
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
-        };
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement opDef = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-
-        validate(opDef);
-
-        CapabilityStatementRestResourceComponent resource = opDef.getRest().get(0).getResource().get(0);
-        assertEquals("DiagnosticReport", resource.getType());
-        List<String> searchParamNames = resource.getSearchParam().stream().map(t -> t.getName()).collect(Collectors.toList());
-        assertThat(searchParamNames, containsInAnyOrder("patient.birthdate", "patient.family", "patient.given", "date"));
-    }
+		CapabilityStatement opDef = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
 
-    @Test
-    public void testIncludeLastUpdatedSearchParam() throws Exception {
+		validate(opDef);
 
-        class MyProvider implements IResourceProvider {
+		// On Patient Resource
+		CapabilityStatementRestResourceComponent resource = opDef.getRest().get(0).getResource().stream().filter(t -> t.getType().equals("Patient")).findFirst().orElseThrow(() -> new IllegalArgumentException());
+		CapabilityStatementRestResourceOperationComponent graphQlOperation = resource.getOperation().stream().filter(t -> t.getName().equals("graphql")).findFirst().orElseThrow(() -> new IllegalArgumentException());
+		assertEquals("graphql", graphQlOperation.getName());
+		assertEquals("http://localhost/baseR4/OperationDefinition/Global-is-graphql", graphQlOperation.getDefinition());
+		assertEquals("This operation invokes a GraphQL expression for fetching an joining a graph of resources, returning them in a custom format.", graphQlOperation.getDocumentation());
+
+		// On Patient Resource
+		resource = opDef.getRest().get(0).getResource().stream().filter(t -> t.getType().equals("Observation")).findFirst().orElseThrow(() -> new IllegalArgumentException());
+		graphQlOperation = resource.getOperation().stream().filter(t -> t.getName().equals("graphql")).findFirst().orElseThrow(() -> new IllegalArgumentException());
+		assertEquals("graphql", graphQlOperation.getName());
+		assertEquals("http://localhost/baseR4/OperationDefinition/Global-is-graphql", graphQlOperation.getDefinition());
+		assertEquals("This operation invokes a GraphQL expression for fetching an joining a graph of resources, returning them in a custom format.", graphQlOperation.getDocumentation());
+
+		// At Server Level
+		CapabilityStatementRestComponent rest = opDef.getRest().get(0);
+		graphQlOperation = rest.getOperation().stream().filter(t -> t.getName().equals("graphql")).findAny().orElseThrow(() -> new IllegalArgumentException());
+		assertEquals("graphql", graphQlOperation.getName());
+		assertEquals("http://localhost/baseR4/OperationDefinition/Global-is-graphql", graphQlOperation.getDefinition());
+		assertEquals("This operation invokes a GraphQL expression for fetching an joining a graph of resources, returning them in a custom format.", graphQlOperation.getDocumentation());
+
+		// Fetch OperationDefinition
+		IdType id = new IdType("http://localhost/baseR4/OperationDefinition/Global-is-graphql");
+		RequestDetails requestDetails = createRequestDetails(rs);
+		OperationDefinition operationDefinition = (OperationDefinition) sc.readOperationDefinition(id, requestDetails);
+		assertEquals("Graphql", operationDefinition.getName());
+		assertEquals("graphql", operationDefinition.getCode());
+		assertEquals("http://localhost/baseR4/OperationDefinition/Global-is-graphql", operationDefinition.getUrl());
+		assertEquals("This operation invokes a GraphQL expression for fetching an joining a graph of resources, returning them in a custom format.", operationDefinition.getDescription());
+		assertTrue(operationDefinition.getSystem());
+		assertFalse(operationDefinition.getType());
+		assertTrue(operationDefinition.getInstance());
+	}
+
+
+	@Test
+	public void testPlainProviderGlobalSystemAndInstanceOperations() throws Exception {
+
+		class MyProvider {
+
+			@Description(
+				value = "This operation examines two resource versions (can be two versions of the same resource, or two different resources) and generates a FHIR Patch document showing the differences.",
+				shortDefinition = "Comparte two resources or two versions of a single resource")
+			@Operation(name = ProviderConstants.DIFF_OPERATION_NAME, global = true, idempotent = true)
+			public IBaseParameters diff(
+				@IdParam IIdType theResourceId,
+
+				@Description(value = "The resource ID and version to diff from", example = "Patient/example/version/1")
+				@OperationParam(name = ProviderConstants.DIFF_FROM_VERSION_PARAMETER, typeName = "string", min = 0, max = 1)
+					IPrimitiveType<?> theFromVersion,
+
+				@Description(value = "Should differences in the Resource.meta element be included in the diff", example = "false")
+				@OperationParam(name = ProviderConstants.DIFF_INCLUDE_META_PARAMETER, typeName = "boolean", min = 0, max = 1)
+					IPrimitiveType<Boolean> theIncludeMeta,
+				RequestDetails theRequestDetails) {
+				throw new IllegalStateException();
+			}
+
+			@Description("This operation examines two resource versions (can be two versions of the same resource, or two different resources) and generates a FHIR Patch document showing the differences.")
+			@Operation(name = ProviderConstants.DIFF_OPERATION_NAME, idempotent = true)
+			public IBaseParameters diff(
+				@Description(value = "The resource ID and version to diff from", example = "Patient/example/version/1")
+				@OperationParam(name = ProviderConstants.DIFF_FROM_PARAMETER, typeName = "id", min = 1, max = 1)
+					IIdType theFromVersion,
+
+				@Description(value = "The resource ID and version to diff to", example = "Patient/example/version/2")
+				@OperationParam(name = ProviderConstants.DIFF_TO_PARAMETER, typeName = "id", min = 1, max = 1)
+					IIdType theToVersion,
+
+				@Description(value = "Should differences in the Resource.meta element be included in the diff", example = "false")
+				@OperationParam(name = ProviderConstants.DIFF_INCLUDE_META_PARAMETER, typeName = "boolean", min = 0, max = 1)
+					IPrimitiveType<Boolean> theIncludeMeta,
+				RequestDetails theRequestDetails) {
+				throw new IllegalStateException();
+			}
+
+		}
+
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
+		rs.registerProvider(new MyProvider());
+		rs.registerProvider(new HashMapResourceProvider<>(myCtx, Patient.class));
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
+		};
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement opDef = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+		validate(opDef);
+
+		// On Patient Resource
+		CapabilityStatementRestResourceComponent resource = opDef.getRest().get(0).getResource().stream().filter(t -> t.getType().equals("Patient")).findFirst().orElseThrow(() -> new IllegalArgumentException());
+		CapabilityStatementRestResourceOperationComponent operation = resource.getOperation().stream().filter(t -> t.getName().equals("diff")).findFirst().orElseThrow(() -> new IllegalArgumentException());
+		assertEquals("diff", operation.getName());
+		assertEquals("http://localhost/baseR4/OperationDefinition/Global-is-diff", operation.getDefinition());
+		assertEquals("This operation examines two resource versions (can be two versions of the same resource, or two different resources) and generates a FHIR Patch document showing the differences.", operation.getDocumentation());
+
+		// At Server Level
+		CapabilityStatementRestComponent rest = opDef.getRest().get(0);
+		operation = rest.getOperation().stream().filter(t -> t.getName().equals("diff")).findAny().orElse(null);
+		assertEquals("diff", operation.getName());
+		assertEquals("http://localhost/baseR4/OperationDefinition/Global-is-diff", operation.getDefinition());
+		assertEquals("This operation examines two resource versions (can be two versions of the same resource, or two different resources) and generates a FHIR Patch document showing the differences.", operation.getDocumentation());
+
+		// Fetch OperationDefinition
+		IdType id = new IdType("http://localhost/baseR4/OperationDefinition/Global-is-diff");
+		RequestDetails requestDetails = createRequestDetails(rs);
+		OperationDefinition operationDefinition = (OperationDefinition) sc.readOperationDefinition(id, requestDetails);
+		assertEquals("Diff", operationDefinition.getName());
+		assertEquals("diff", operationDefinition.getCode());
+		assertEquals("http://localhost/baseR4/OperationDefinition/Global-is-diff", operationDefinition.getUrl());
+		assertEquals("This operation examines two resource versions (can be two versions of the same resource, or two different resources) and generates a FHIR Patch document showing the differences.", operationDefinition.getDescription());
+		assertTrue(operationDefinition.getSystem());
+		assertFalse(operationDefinition.getType());
+		assertTrue(operationDefinition.getInstance());
+	}
+
+	@Test
+	public void testResourceProviderTypeAndInstanceLevelOperations() throws Exception {
+
+		class MyProvider implements IResourceProvider {
+
+			@Description("This is the expunge operation")
+			@Operation(name = "expunge", idempotent = false, returnParameters = {
+				@OperationParam(name = "count", typeName = "integer")
+			})
+			public IBaseParameters expunge(
+				@IdParam IIdType theIdParam,
+				@OperationParam(name = "limit", typeName = "integer") IPrimitiveType<Integer> theLimit,
+				@OperationParam(name = "deleted", typeName = "boolean") IPrimitiveType<Boolean> theExpungeDeletedResources,
+				@OperationParam(name = "previous", typeName = "boolean") IPrimitiveType<Boolean> theExpungeOldVersions,
+				RequestDetails theRequest) {
+				throw new UnsupportedOperationException();
+			}
+
+			@Description("This is the expunge operation")
+			@Operation(name = "expunge", idempotent = false, returnParameters = {
+				@OperationParam(name = "count", typeName = "integer")
+			})
+			public IBaseParameters expunge(
+				@OperationParam(name = "limit", typeName = "integer") IPrimitiveType<Integer> theLimit,
+				@OperationParam(name = "deleted", typeName = "boolean") IPrimitiveType<Boolean> theExpungeDeletedResources,
+				@OperationParam(name = "previous", typeName = "boolean") IPrimitiveType<Boolean> theExpungeOldVersions,
+				RequestDetails theRequest) {
+				throw new UnsupportedOperationException();
+			}
+
+			@Override
+			public Class<? extends IBaseResource> getResourceType() {
+				return Patient.class;
+			}
+		}
+
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
+		rs.registerProvider(new MyProvider());
+		rs.registerProvider(new HashMapResourceProvider<>(myCtx, Patient.class));
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
+		};
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement opDef = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+		validate(opDef);
+
+		// On Patient Resource
+		CapabilityStatementRestResourceComponent resource = opDef.getRest().get(0).getResource().stream().filter(t -> t.getType().equals("Patient")).findFirst().orElseThrow(() -> new IllegalArgumentException());
+		List<CapabilityStatementRestResourceOperationComponent> expungeResourceOperations = resource.getOperation().stream().filter(t -> t.getName().equals("expunge")).collect(Collectors.toList());
+		assertEquals(1, expungeResourceOperations.size());
+		CapabilityStatementRestResourceOperationComponent operation = expungeResourceOperations.get(0);
+		assertEquals("expunge", operation.getName());
+		assertEquals("http://localhost/baseR4/OperationDefinition/Patient-it-expunge", operation.getDefinition());
+		assertEquals("This is the expunge operation", operation.getDocumentation());
+
+		// At Server Level
+		CapabilityStatementRestComponent rest = opDef.getRest().get(0);
+		operation = rest.getOperation().stream().filter(t -> t.getName().equals("expunge")).findAny().orElse(null);
+		assertNull(operation);
+
+		// Fetch OperationDefinition
+		IdType id = new IdType("http://localhost/baseR4/OperationDefinition/Patient-it-expunge");
+		RequestDetails requestDetails = createRequestDetails(rs);
+		OperationDefinition operationDefinition = (OperationDefinition) sc.readOperationDefinition(id, requestDetails);
+		assertEquals("Expunge", operationDefinition.getName());
+		assertEquals("expunge", operationDefinition.getCode());
+		assertEquals("http://localhost/baseR4/OperationDefinition/Patient-it-expunge", operationDefinition.getUrl());
+		assertEquals("This is the expunge operation", operationDefinition.getDescription());
+		assertFalse(operationDefinition.getSystem());
+		assertTrue(operationDefinition.getType());
+		assertTrue(operationDefinition.getInstance());
+
+	}
+
+	@Test
+	public void testIncludeLastUpdatedSearchParam() throws Exception {
+
+		class MyProvider implements IResourceProvider {
+
+			@Override
+			public Class<DiagnosticReport> getResourceType() {
+				return DiagnosticReport.class;
+			}
+
+			@Search
+			public List<IBaseResource> search(@OptionalParam(name = DiagnosticReport.SP_DATE)
+															 DateRangeParam range,
+
+														 @Description(shortDefinition = "Only return resources which were last updated as specified by the given range")
+														 @OptionalParam(name = "_lastUpdated")
+															 DateRangeParam theLastUpdated
+			) {
+				return null;
+			}
+
+		}
+
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new MyProvider());
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
+		};
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement opDef = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+		validate(opDef);
+
+		CapabilityStatementRestResourceComponent resource = opDef.getRest().get(0).getResource().get(0);
+		assertEquals("DiagnosticReport", resource.getType());
+		List<String> searchParamNames = resource.getSearchParam().stream().map(t -> t.getName()).collect(Collectors.toList());
+		assertThat(searchParamNames, containsInAnyOrder("date", "_lastUpdated"));
+	}
+
+	@Test
+	public void testSystemLevelNamedQueryWithParameters() throws Exception {
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new NamedQueryPlainProvider());
+		rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		validate(conformance);
+
+		CapabilityStatementRestResourceComponent patient = conformance.getRestFirstRep().getResource().stream().filter(t -> t.getType().equals("Patient")).findFirst().orElseThrow(() -> new IllegalArgumentException());
+		CapabilityStatementRestResourceOperationComponent operationComponent = patient.getOperation().get(0);
+		assertThat(operationComponent.getName(), is(NamedQueryPlainProvider.QUERY_NAME));
+
+		String operationReference = operationComponent.getDefinition();
+		assertThat(operationReference, not(nullValue()));
+
+		OperationDefinition operationDefinition = (OperationDefinition) sc.readOperationDefinition(new IdType(operationReference), createRequestDetails(rs));
+		ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
+		validate(operationDefinition);
+		assertThat(operationDefinition.getCode(), is(NamedQueryPlainProvider.QUERY_NAME));
+		assertThat(operationDefinition.getName(), is("TestQuery"));
+		assertThat(operationDefinition.getStatus(), is(PublicationStatus.ACTIVE));
+		assertThat(operationDefinition.getKind(), is(OperationKind.QUERY));
+		assertThat(operationDefinition.getDescription(), is(NamedQueryPlainProvider.DESCRIPTION));
+		assertThat(operationDefinition.getAffectsState(), is(false));
+		assertThat("A system level search has no target resources", operationDefinition.getResource(), is(empty()));
+		assertThat(operationDefinition.getSystem(), is(true));
+		assertThat(operationDefinition.getType(), is(false));
+		assertThat(operationDefinition.getInstance(), is(false));
+		List<OperationDefinitionParameterComponent> parameters = operationDefinition.getParameter();
+		assertThat(parameters.size(), is(1));
+		OperationDefinitionParameterComponent param = parameters.get(0);
+		assertThat(param.getName(), is(NamedQueryPlainProvider.SP_QUANTITY));
+		assertThat(param.getType(), is("string"));
+		assertThat(param.getSearchTypeElement().asStringValue(), is(RestSearchParameterTypeEnum.QUANTITY.getCode()));
+		assertThat(param.getMin(), is(1));
+		assertThat(param.getMax(), is("1"));
+		assertThat(param.getUse(), is(OperationParameterUse.IN));
+	}
+
+	@Test
+	public void testResourceLevelNamedQueryWithParameters() throws Exception {
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new NamedQueryResourceProvider());
+		rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		validate(conformance);
+
+		CapabilityStatementRestResourceComponent restComponent = conformance.getRestFirstRep().getResource().stream().filter(t -> t.getType().equals("Patient")).findFirst().orElseThrow(() -> new IllegalArgumentException());
+		CapabilityStatementRestResourceOperationComponent operationComponent = restComponent.getOperation().get(0);
+		String operationReference = operationComponent.getDefinition();
+		assertThat(operationReference, not(nullValue()));
+
+		OperationDefinition operationDefinition = (OperationDefinition) sc.readOperationDefinition(new IdType(operationReference), createRequestDetails(rs));
+		ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
+		validate(operationDefinition);
+		assertThat("The operation name should be the code if no description is set", operationDefinition.getName(), is("TestQuery"));
+		String patientResourceName = "Patient";
+		assertThat("A resource level search targets the resource of the provider it's defined in", operationDefinition.getResource().get(0).getValue(), is(patientResourceName));
+		assertThat(operationDefinition.getSystem(), is(false));
+		assertThat(operationDefinition.getType(), is(true));
+		assertThat(operationDefinition.getInstance(), is(false));
+		List<OperationDefinitionParameterComponent> parameters = operationDefinition.getParameter();
+		assertThat(parameters.size(), is(1));
+		OperationDefinitionParameterComponent param = parameters.get(0);
+		assertThat(param.getName(), is(NamedQueryResourceProvider.SP_PARAM));
+		assertThat(param.getType(), is("string"));
+		assertThat(param.getSearchTypeElement().asStringValue(), is(RestSearchParameterTypeEnum.STRING.getCode()));
+		assertThat(param.getMin(), is(0));
+		assertThat(param.getMax(), is("1"));
+		assertThat(param.getUse(), is(OperationParameterUse.IN));
+
+		List<CapabilityStatementRestResourceSearchParamComponent> patientResource = restComponent.getSearchParam();
+		assertThat("Named query parameters should not appear in the resource search params", patientResource, is(empty()));
+	}
+
+	@Test
+	public void testExtendedOperationAtTypeLevel() throws Exception {
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setProviders(new TypeLevelOperationProvider());
+		rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+		validate(conformance);
+
+		CapabilityStatementRestResourceComponent patient = conformance.getRestFirstRep().getResource().stream().filter(t -> t.getType().equals("Patient")).findFirst().orElseThrow(() -> new IllegalArgumentException());
+		List<CapabilityStatementRestResourceOperationComponent> operations = patient.getOperation();
+		assertThat(operations.size(), is(1));
+		assertThat(operations.get(0).getName(), is(TypeLevelOperationProvider.OPERATION_NAME));
+
+		OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType(operations.get(0).getDefinition()), createRequestDetails(rs));
+		validate(opDef);
+		assertEquals(TypeLevelOperationProvider.OPERATION_NAME, opDef.getCode());
+		assertThat(opDef.getSystem(), is(false));
+		assertThat(opDef.getType(), is(true));
+		assertThat(opDef.getInstance(), is(false));
+	}
+
+	@Test
+	public void testProfiledResourceStructureDefinitionLinks() throws Exception {
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setResourceProviders(new ProfiledPatientProvider(), new MultipleProfilesPatientProvider());
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
+
+		List<CapabilityStatementRestResourceComponent> resources = conformance.getRestFirstRep().getResource();
+		CapabilityStatementRestResourceComponent patientResource = resources.stream()
+			.filter(resource -> "Patient".equals(resource.getType()))
+			.findFirst().get();
+		assertThat(patientResource.getProfile(), containsString(PATIENT_SUB));
+	}
+
+	@Test
+	public void testRevIncludes_Explicit() throws Exception {
+
+		class PatientResourceProvider implements IResourceProvider {
+
+			@Override
+			public Class<Patient> getResourceType() {
+				return Patient.class;
+			}
+
+			@Search
+			public List<Patient> search(@IncludeParam(reverse = true, allow = {"Observation:foo", "Provenance:bar"}) Set<Include> theRevIncludes) {
+				return Collections.emptyList();
+			}
+
+		}
+
+		class ObservationResourceProvider implements IResourceProvider {
+
+			@Override
+			public Class<Observation> getResourceType() {
+				return Observation.class;
+			}
+
+			@Search
+			public List<Observation> search(@OptionalParam(name = "subject") ReferenceParam theSubject) {
+				return Collections.emptyList();
+			}
+
+		}
+
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setResourceProviders(new PatientResourceProvider(), new ObservationResourceProvider());
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		sc.setRestResourceRevIncludesEnabled(true);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
+
+		List<CapabilityStatementRestResourceComponent> resources = conformance.getRestFirstRep().getResource();
+		CapabilityStatementRestResourceComponent patientResource = resources.stream()
+			.filter(resource -> "Patient".equals(resource.getType()))
+			.findFirst().get();
+		assertThat(toStrings(patientResource.getSearchRevInclude()), containsInAnyOrder("Observation:foo", "Provenance:bar"));
+	}
+
+	@Test
+	public void testRevIncludes_Inferred() throws Exception {
+
+		class PatientResourceProvider implements IResourceProvider {
+
+			@Override
+			public Class<Patient> getResourceType() {
+				return Patient.class;
+			}
+
+			@Search
+			public List<Patient> search(@IncludeParam(reverse = true) Set<Include> theRevIncludes) {
+				return Collections.emptyList();
+			}
+
+		}
+
+		class ObservationResourceProvider implements IResourceProvider {
+
+			@Override
+			public Class<Observation> getResourceType() {
+				return Observation.class;
+			}
+
+			@Search
+			public List<Observation> search(@OptionalParam(name = "subject") ReferenceParam theSubject) {
+				return Collections.emptyList();
+			}
+
+		}
+
+		RestfulServer rs = new RestfulServer(myCtx);
+		rs.setResourceProviders(new PatientResourceProvider(), new ObservationResourceProvider());
+
+		ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+		sc.setRestResourceRevIncludesEnabled(true);
+		rs.setServerConformanceProvider(sc);
+
+		rs.init(MockServletUtil.createServletConfig());
+
+		CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+		ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
+
+		List<CapabilityStatementRestResourceComponent> resources = conformance.getRestFirstRep().getResource();
+		CapabilityStatementRestResourceComponent patientResource = resources.stream()
+			.filter(resource -> "Patient".equals(resource.getType()))
+			.findFirst().get();
+		assertThat(toStrings(patientResource.getSearchRevInclude()), containsInAnyOrder("Observation:subject"));
+	}
+
+	private List<String> toOperationIdParts(List<CapabilityStatementRestResourceOperationComponent> theOperation) {
+		ArrayList<String> retVal = Lists.newArrayList();
+		for (CapabilityStatementRestResourceOperationComponent next : theOperation) {
+			retVal.add(new IdType(next.getDefinition()).getIdPart());
+		}
+		return retVal;
+	}
+
+	private List<String> toOperationNames(List<CapabilityStatementRestResourceOperationComponent> theOperation) {
+		ArrayList<String> retVal = Lists.newArrayList();
+		for (CapabilityStatementRestResourceOperationComponent next : theOperation) {
+			retVal.add(next.getName());
+		}
+		return retVal;
+	}
+
+	private List<String> toOperationDefinitions(List<CapabilityStatementRestResourceOperationComponent> theOperation) {
+		ArrayList<String> retVal = Lists.newArrayList();
+		for (CapabilityStatementRestResourceOperationComponent next : theOperation) {
+			retVal.add(next.getDefinition());
+		}
+		return retVal;
+	}
+
+	private String validate(IBaseResource theResource) {
+		String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(theResource);
+		ourLog.info("Def:\n{}", conf);
+
+		ValidationResult result = myValidator.validateWithResult(conf);
+		OperationOutcome operationOutcome = (OperationOutcome) result.toOperationOutcome();
+		String outcome = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome);
+		ourLog.info("Outcome: {}", outcome);
+
+		assertTrue(result.isSuccessful(), outcome);
+		List<OperationOutcome.OperationOutcomeIssueComponent> warningsAndErrors = operationOutcome
+			.getIssue()
+			.stream()
+			.filter(t -> t.getSeverity().ordinal() <= OperationOutcome.IssueSeverity.WARNING.ordinal()) // <= because this enum has a strange order
+			.collect(Collectors.toList());
+		assertThat(outcome, warningsAndErrors, is(empty()));
+
+		return myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(theResource);
+	}
+
+	@SuppressWarnings("unused")
+	public static class ConditionalProvider implements IResourceProvider {
+
+		@Create
+		public MethodOutcome create(@ResourceParam Patient thePatient, @ConditionalUrlParam String theConditionalUrl) {
+			return null;
+		}
+
+		@Delete
+		public MethodOutcome delete(@IdParam IdType theId, @ConditionalUrlParam(supportsMultiple = true) String theConditionalUrl) {
+			return null;
+		}
+
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return Patient.class;
+		}
+
+		@Update
+		public MethodOutcome update(@IdParam IdType theId, @ResourceParam Patient thePatient, @ConditionalUrlParam String theConditionalUrl) {
+			return null;
+		}
+
+	}
+
+	@SuppressWarnings("unused")
+	public static class InstanceHistoryProvider implements IResourceProvider {
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return Patient.class;
+		}
+
+		@History
+		public List<IBaseResource> history(@IdParam IdType theId) {
+			return null;
+		}
+
+	}
+
+	@SuppressWarnings("unused")
+	public static class MultiOptionalProvider {
+
+		@Search(type = Patient.class)
+		public Patient findPatient(@Description(shortDefinition = "The patient's identifier") @OptionalParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier,
+											@Description(shortDefinition = "The patient's name") @OptionalParam(name = Patient.SP_NAME) StringParam theName) {
+			return null;
+		}
+
+	}
+
+	@SuppressWarnings("unused")
+	public static class MultiTypeEncounterProvider implements IResourceProvider {
+
+		@Operation(name = "someOp")
+		public IBundleProvider everything(HttpServletRequest theServletRequest, @IdParam IdType theId,
+													 @OperationParam(name = "someOpParam1") DateType theStart, @OperationParam(name = "someOpParam2") Encounter theEnd) {
+			return null;
+		}
+
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return Encounter.class;
+		}
+
+		@Validate
+		public IBundleProvider validate(HttpServletRequest theServletRequest, @IdParam IdType theId, @ResourceParam Encounter thePatient) {
+			return null;
+		}
+
+	}
+
+	@SuppressWarnings("unused")
+	public static class MultiTypePatientProvider implements IResourceProvider {
+
+		@Operation(name = "someOp")
+		public IBundleProvider everything(
+			HttpServletRequest theServletRequest,
+
+			@IdParam IdType theId,
+
+			@Description(value = "Start description", example = {"2001", "2002"})
+			@OperationParam(name = "someOpParam1") DateType theStart,
+
+			@OperationParam(name = "someOpParam2") Patient theEnd) {
+			return null;
+		}
 
-            @Override
-            public Class<DiagnosticReport> getResourceType() {
-                return DiagnosticReport.class;
-            }
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return Patient.class;
+		}
+
+		@Validate
+		public IBundleProvider validate(HttpServletRequest theServletRequest, @IdParam IdType theId, @ResourceParam Patient thePatient) {
+			return null;
+		}
 
-            @Search
-            public List<IBaseResource> search(@OptionalParam(name = DiagnosticReport.SP_DATE)
-                                                      DateRangeParam range,
+	}
 
-                                              @Description(shortDefinition = "Only return resources which were last updated as specified by the given range")
-                                              @OptionalParam(name = "_lastUpdated")
-                                                      DateRangeParam theLastUpdated
-            ) {
-                return null;
-            }
-
-        }
-
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new MyProvider());
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
-        };
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement opDef = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-
-        validate(opDef);
-
-        CapabilityStatementRestResourceComponent resource = opDef.getRest().get(0).getResource().get(0);
-        assertEquals("DiagnosticReport", resource.getType());
-        List<String> searchParamNames = resource.getSearchParam().stream().map(t -> t.getName()).collect(Collectors.toList());
-        assertThat(searchParamNames, containsInAnyOrder("date", "_lastUpdated"));
-    }
-
-    @Test
-    public void testSystemLevelNamedQueryWithParameters() throws Exception {
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new NamedQueryPlainProvider());
-        rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        validate(conformance);
-
-        CapabilityStatementRestComponent restComponent = conformance.getRest().get(0);
-        CapabilityStatementRestResourceOperationComponent operationComponent = restComponent.getOperation().get(0);
-        assertThat(operationComponent.getName(), is(NamedQueryPlainProvider.QUERY_NAME));
-
-        String operationReference = operationComponent.getDefinition();
-        assertThat(operationReference, not(nullValue()));
-
-        OperationDefinition operationDefinition = (OperationDefinition) sc.readOperationDefinition(new IdType(operationReference), createRequestDetails(rs));
-        ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
-        validate(operationDefinition);
-        assertThat(operationDefinition.getCode(), is(NamedQueryPlainProvider.QUERY_NAME));
-        assertThat(operationDefinition.getName(), is("Search_" + NamedQueryPlainProvider.QUERY_NAME));
-        assertThat(operationDefinition.getStatus(), is(PublicationStatus.ACTIVE));
-        assertThat(operationDefinition.getKind(), is(OperationKind.QUERY));
-        assertThat(operationDefinition.getDescription(), is(NamedQueryPlainProvider.DESCRIPTION));
-        assertThat(operationDefinition.getAffectsState(), is(false));
-        assertThat("A system level search has no target resources", operationDefinition.getResource(), is(empty()));
-        assertThat(operationDefinition.getSystem(), is(true));
-        assertThat(operationDefinition.getType(), is(false));
-        assertThat(operationDefinition.getInstance(), is(false));
-        List<OperationDefinitionParameterComponent> parameters = operationDefinition.getParameter();
-        assertThat(parameters.size(), is(1));
-        OperationDefinitionParameterComponent param = parameters.get(0);
-        assertThat(param.getName(), is(NamedQueryPlainProvider.SP_QUANTITY));
-        assertThat(param.getType(), is("string"));
-        assertThat(param.getSearchTypeElement().asStringValue(), is(RestSearchParameterTypeEnum.QUANTITY.getCode()));
-        assertThat(param.getMin(), is(1));
-        assertThat(param.getMax(), is("1"));
-        assertThat(param.getUse(), is(OperationParameterUse.IN));
-    }
-
-    @Test
-    public void testResourceLevelNamedQueryWithParameters() throws Exception {
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new NamedQueryResourceProvider());
-        rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        validate(conformance);
-
-        CapabilityStatementRestComponent restComponent = conformance.getRest().get(0);
-        CapabilityStatementRestResourceOperationComponent operationComponent = restComponent.getOperation().get(0);
-        String operationReference = operationComponent.getDefinition();
-        assertThat(operationReference, not(nullValue()));
-
-        OperationDefinition operationDefinition = (OperationDefinition) sc.readOperationDefinition(new IdType(operationReference), createRequestDetails(rs));
-        ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
-        validate(operationDefinition);
-        assertThat("The operation name should be the code if no description is set", operationDefinition.getName(), is("Search_" + NamedQueryResourceProvider.QUERY_NAME));
-        String patientResourceName = "Patient";
-        assertThat("A resource level search targets the resource of the provider it's defined in", operationDefinition.getResource().get(0).getValue(), is(patientResourceName));
-        assertThat(operationDefinition.getSystem(), is(false));
-        assertThat(operationDefinition.getType(), is(true));
-        assertThat(operationDefinition.getInstance(), is(false));
-        List<OperationDefinitionParameterComponent> parameters = operationDefinition.getParameter();
-        assertThat(parameters.size(), is(1));
-        OperationDefinitionParameterComponent param = parameters.get(0);
-        assertThat(param.getName(), is(NamedQueryResourceProvider.SP_PARAM));
-        assertThat(param.getType(), is("string"));
-        assertThat(param.getSearchTypeElement().asStringValue(), is(RestSearchParameterTypeEnum.STRING.getCode()));
-        assertThat(param.getMin(), is(0));
-        assertThat(param.getMax(), is("1"));
-        assertThat(param.getUse(), is(OperationParameterUse.IN));
-
-        CapabilityStatementRestResourceComponent patientResource = restComponent.getResource().stream()
-                .filter(r -> patientResourceName.equals(r.getType()))
-                .findAny().get();
-        assertThat("Named query parameters should not appear in the resource search params", patientResource.getSearchParam(), is(empty()));
-    }
-
-    @Test
-    public void testExtendedOperationAtTypeLevel() throws Exception {
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setProviders(new TypeLevelOperationProvider());
-        rs.setServerAddressStrategy(new HardcodedServerAddressStrategy("http://localhost/baseR4"));
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-
-        validate(conformance);
-
-        List<CapabilityStatementRestResourceOperationComponent> operations = conformance.getRest().get(0).getOperation();
-        assertThat(operations.size(), is(1));
-        assertThat(operations.get(0).getName(), is(TypeLevelOperationProvider.OPERATION_NAME));
-
-        OperationDefinition opDef = (OperationDefinition) sc.readOperationDefinition(new IdType(operations.get(0).getDefinition()), createRequestDetails(rs));
-        validate(opDef);
-        assertEquals(TypeLevelOperationProvider.OPERATION_NAME, opDef.getCode());
-        assertThat(opDef.getSystem(), is(false));
-        assertThat(opDef.getType(), is(true));
-        assertThat(opDef.getInstance(), is(false));
-    }
-
-    @Test
-    public void testProfiledResourceStructureDefinitionLinks() throws Exception {
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setResourceProviders(new ProfiledPatientProvider(), new MultipleProfilesPatientProvider());
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
-
-        List<CapabilityStatementRestResourceComponent> resources = conformance.getRestFirstRep().getResource();
-        CapabilityStatementRestResourceComponent patientResource = resources.stream()
-                .filter(resource -> "Patient".equals(resource.getType()))
-                .findFirst().get();
-        assertThat(patientResource.getProfile(), containsString(PATIENT_SUB));
-    }
-
-    @Test
-    public void testRevIncludes_Explicit() throws Exception {
-
-        class PatientResourceProvider implements IResourceProvider {
-
-            @Override
-            public Class<Patient> getResourceType() {
-                return Patient.class;
-            }
-
-            @Search
-            public List<Patient> search(@IncludeParam(reverse = true, allow = {"Observation:foo", "Provenance:bar"}) Set<Include> theRevIncludes) {
-                return Collections.emptyList();
-            }
-
-        }
-
-        class ObservationResourceProvider implements IResourceProvider {
-
-            @Override
-            public Class<Observation> getResourceType() {
-                return Observation.class;
-            }
-
-            @Search
-            public List<Observation> search(@OptionalParam(name = "subject") ReferenceParam theSubject) {
-                return Collections.emptyList();
-            }
-
-        }
-
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setResourceProviders(new PatientResourceProvider(), new ObservationResourceProvider());
-
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        sc.setRestResourceRevIncludesEnabled(true);
-        rs.setServerConformanceProvider(sc);
-
-        rs.init(createServletConfig());
-
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
-
-        List<CapabilityStatementRestResourceComponent> resources = conformance.getRestFirstRep().getResource();
-        CapabilityStatementRestResourceComponent patientResource = resources.stream()
-                .filter(resource -> "Patient".equals(resource.getType()))
-                .findFirst().get();
-        assertThat(toStrings(patientResource.getSearchRevInclude()), containsInAnyOrder("Observation:foo", "Provenance:bar"));
-    }
+	@SuppressWarnings("unused")
+	public static class NonConditionalProvider implements IResourceProvider {
 
-    @Test
-    public void testRevIncludes_Inferred() throws Exception {
+		@Create
+		public MethodOutcome create(@ResourceParam Patient thePatient) {
+			return null;
+		}
 
-        class PatientResourceProvider implements IResourceProvider {
+		@Delete
+		public MethodOutcome delete(@IdParam IdType theId) {
+			return null;
+		}
 
-            @Override
-            public Class<Patient> getResourceType() {
-                return Patient.class;
-            }
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return Patient.class;
+		}
 
-            @Search
-            public List<Patient> search(@IncludeParam(reverse = true) Set<Include> theRevIncludes) {
-                return Collections.emptyList();
-            }
-
-        }
-
-        class ObservationResourceProvider implements IResourceProvider {
-
-            @Override
-            public Class<Observation> getResourceType() {
-                return Observation.class;
-            }
-
-            @Search
-            public List<Observation> search(@OptionalParam(name = "subject") ReferenceParam theSubject) {
-                return Collections.emptyList();
-            }
+		@Update
+		public MethodOutcome update(@IdParam IdType theId, @ResourceParam Patient thePatient) {
+			return null;
+		}
 
-        }
-
-        RestfulServer rs = new RestfulServer(myCtx);
-        rs.setResourceProviders(new PatientResourceProvider(), new ObservationResourceProvider());
+	}
 
-        ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
-        sc.setRestResourceRevIncludesEnabled(true);
-        rs.setServerConformanceProvider(sc);
+	@SuppressWarnings("unused")
+	public static class ProviderWithExtendedOperationReturningBundle implements IResourceProvider {
 
-        rs.init(createServletConfig());
+		@Operation(name = "everything", idempotent = true)
+		public IBundleProvider everything(HttpServletRequest theServletRequest, @IdParam IdType theId, @OperationParam(name = "start") DateType theStart,
+													 @OperationParam(name = "end") DateType theEnd) {
+			return null;
+		}
 
-        CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
-        ourLog.info(myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
-
-        List<CapabilityStatementRestResourceComponent> resources = conformance.getRestFirstRep().getResource();
-        CapabilityStatementRestResourceComponent patientResource = resources.stream()
-                .filter(resource -> "Patient".equals(resource.getType()))
-                .findFirst().get();
-        assertThat(toStrings(patientResource.getSearchRevInclude()), containsInAnyOrder("Observation:subject"));
-    }
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return Patient.class;
+		}
 
-    private List<String> toOperationIdParts(List<CapabilityStatementRestResourceOperationComponent> theOperation) {
-        ArrayList<String> retVal = Lists.newArrayList();
-        for (CapabilityStatementRestResourceOperationComponent next : theOperation) {
-            retVal.add(new IdType(next.getDefinition()).getIdPart());
-        }
-        return retVal;
-    }
-
-    private List<String> toOperationNames(List<CapabilityStatementRestResourceOperationComponent> theOperation) {
-        ArrayList<String> retVal = Lists.newArrayList();
-        for (CapabilityStatementRestResourceOperationComponent next : theOperation) {
-            retVal.add(next.getName());
-        }
-        return retVal;
-    }
-
-    private String validate(IBaseResource theResource) {
-        String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(theResource);
-        ourLog.info("Def:\n{}", conf);
-
-        ValidationResult result = myValidator.validateWithResult(conf);
-        OperationOutcome operationOutcome = (OperationOutcome) result.toOperationOutcome();
-        String outcome = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome);
-        ourLog.info("Outcome: {}", outcome);
-
-        assertTrue(result.isSuccessful(), outcome);
-        List<OperationOutcome.OperationOutcomeIssueComponent> warningsAndErrors = operationOutcome
-                .getIssue()
-                .stream()
-                .filter(t -> t.getSeverity().ordinal() <= OperationOutcome.IssueSeverity.WARNING.ordinal()) // <= because this enum has a strange order
-                .collect(Collectors.toList());
-        assertThat(outcome, warningsAndErrors, is(empty()));
-
-        return myCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(theResource);
-    }
-
-    @SuppressWarnings("unused")
-    public static class ConditionalProvider implements IResourceProvider {
-
-        @Create
-        public MethodOutcome create(@ResourceParam Patient thePatient, @ConditionalUrlParam String theConditionalUrl) {
-            return null;
-        }
-
-        @Delete
-        public MethodOutcome delete(@IdParam IdType theId, @ConditionalUrlParam(supportsMultiple = true) String theConditionalUrl) {
-            return null;
-        }
-
-        @Override
-        public Class<? extends IBaseResource> getResourceType() {
-            return Patient.class;
-        }
-
-        @Update
-        public MethodOutcome update(@IdParam IdType theId, @ResourceParam Patient thePatient, @ConditionalUrlParam String theConditionalUrl) {
-            return null;
-        }
-
-    }
-
-    @SuppressWarnings("unused")
-    public static class InstanceHistoryProvider implements IResourceProvider {
-        @Override
-        public Class<? extends IBaseResource> getResourceType() {
-            return Patient.class;
-        }
-
-        @History
-        public List<IBaseResource> history(@IdParam IdType theId) {
-            return null;
-        }
-
-    }
-
-    @SuppressWarnings("unused")
-    public static class MultiOptionalProvider {
-
-        @Search(type = Patient.class)
-        public Patient findPatient(@Description(shortDefinition = "The patient's identifier") @OptionalParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier,
-                                   @Description(shortDefinition = "The patient's name") @OptionalParam(name = Patient.SP_NAME) StringParam theName) {
-            return null;
-        }
-
-    }
-
-    @SuppressWarnings("unused")
-    public static class MultiTypeEncounterProvider implements IResourceProvider {
-
-        @Operation(name = "someOp")
-        public IBundleProvider everything(HttpServletRequest theServletRequest, @IdParam IdType theId,
-                                          @OperationParam(name = "someOpParam1") DateType theStart, @OperationParam(name = "someOpParam2") Encounter theEnd) {
-            return null;
-        }
-
-        @Override
-        public Class<? extends IBaseResource> getResourceType() {
-            return Encounter.class;
-        }
-
-        @Validate
-        public IBundleProvider validate(HttpServletRequest theServletRequest, @IdParam IdType theId, @ResourceParam Encounter thePatient) {
-            return null;
-        }
-
-    }
-
-    @SuppressWarnings("unused")
-    public static class MultiTypePatientProvider implements IResourceProvider {
-
-        @Operation(name = "someOp")
-        public IBundleProvider everything(HttpServletRequest theServletRequest, @IdParam IdType theId,
-                                          @OperationParam(name = "someOpParam1") DateType theStart, @OperationParam(name = "someOpParam2") Patient theEnd) {
-            return null;
-        }
-
-        @Override
-        public Class<? extends IBaseResource> getResourceType() {
-            return Patient.class;
-        }
-
-        @Validate
-        public IBundleProvider validate(HttpServletRequest theServletRequest, @IdParam IdType theId, @ResourceParam Patient thePatient) {
-            return null;
-        }
-
-    }
-
-    @SuppressWarnings("unused")
-    public static class NonConditionalProvider implements IResourceProvider {
-
-        @Create
-        public MethodOutcome create(@ResourceParam Patient thePatient) {
-            return null;
-        }
+	}
 
-        @Delete
-        public MethodOutcome delete(@IdParam IdType theId) {
-            return null;
-        }
-
-        @Override
-        public Class<? extends IBaseResource> getResourceType() {
-            return Patient.class;
-        }
+	@SuppressWarnings("unused")
+	public static class ProviderWithRequiredAndOptional {
 
-        @Update
-        public MethodOutcome update(@IdParam IdType theId, @ResourceParam Patient thePatient) {
-            return null;
-        }
+		@Description(shortDefinition = "This is a search for stuff!")
+		@Search
+		public List<DiagnosticReport> findDiagnosticReportsByPatient(@RequiredParam(name = DiagnosticReport.SP_SUBJECT + '.' + Patient.SP_IDENTIFIER) TokenParam thePatientId,
+																						 @OptionalParam(name = DiagnosticReport.SP_CODE) TokenOrListParam theNames, @OptionalParam(name = DiagnosticReport.SP_DATE) DateRangeParam theDateRange,
+																						 @IncludeParam(allow = {"DiagnosticReport.result"}) Set<Include> theIncludes) throws Exception {
+			return null;
+		}
 
-    }
+	}
 
-    @SuppressWarnings("unused")
-    public static class PlainProviderWithExtendedOperationOnNoType {
+	@SuppressWarnings("unused")
+	public static class ReadProvider {
 
-        @Operation(name = "plain", idempotent = true, returnParameters = {@OperationParam(min = 1, max = 2, name = "out1", type = StringType.class)})
-        public IBundleProvider everything(HttpServletRequest theServletRequest, @IdParam IdType theId, @OperationParam(name = "start") DateType theStart,
-                                          @OperationParam(name = "end") DateType theEnd) {
-            return null;
-        }
+		@Search(type = Patient.class)
+		public Patient findPatient(@Description(shortDefinition = "The patient's identifier (MRN or other card number)") @RequiredParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier) {
+			return null;
+		}
 
-    }
+		@Read(version = false)
+		public Patient readPatient(@IdParam IdType theId) {
+			return null;
+		}
 
-    @SuppressWarnings("unused")
-    public static class ProviderWithExtendedOperationReturningBundle implements IResourceProvider {
+	}
 
-        @Operation(name = "everything", idempotent = true)
-        public IBundleProvider everything(HttpServletRequest theServletRequest, @IdParam IdType theId, @OperationParam(name = "start") DateType theStart,
-                                          @OperationParam(name = "end") DateType theEnd) {
-            return null;
-        }
+	@SuppressWarnings("unused")
+	public static class SearchProvider {
 
-        @Override
-        public Class<? extends IBaseResource> getResourceType() {
-            return Patient.class;
-        }
+		@Search(type = Patient.class)
+		public Patient findPatient1(@Description(shortDefinition = "The patient's identifier (MRN or other card number)") @RequiredParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier) {
+			return null;
+		}
 
-    }
+		@Search(type = Patient.class)
+		public Patient findPatient2(
+			@Description(shortDefinition = "All patients linked to the given patient") @OptionalParam(name = "link", targetTypes = {Patient.class}) ReferenceAndListParam theLink) {
+			return null;
+		}
 
-    @SuppressWarnings("unused")
-    public static class ProviderWithRequiredAndOptional {
+	}
 
-        @Description(shortDefinition = "This is a search for stuff!")
-        @Search
-        public List<DiagnosticReport> findDiagnosticReportsByPatient(@RequiredParam(name = DiagnosticReport.SP_SUBJECT + '.' + Patient.SP_IDENTIFIER) TokenParam thePatientId,
-                                                                     @OptionalParam(name = DiagnosticReport.SP_CODE) TokenOrListParam theNames, @OptionalParam(name = DiagnosticReport.SP_DATE) DateRangeParam theDateRange,
-                                                                     @IncludeParam(allow = {"DiagnosticReport.result"}) Set<Include> theIncludes) throws Exception {
-            return null;
-        }
+	@SuppressWarnings("unused")
+	public static class SearchProviderWithWhitelist {
 
-    }
+		@Search(type = Patient.class)
+		public Patient findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION, chainWhitelist = {"foo",
+			"bar"}) ReferenceAndListParam theIdentifier) {
+			return null;
+		}
 
-    @SuppressWarnings("unused")
-    public static class ReadProvider {
+	}
 
-        @Search(type = Patient.class)
-        public Patient findPatient(@Description(shortDefinition = "The patient's identifier (MRN or other card number)") @RequiredParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier) {
-            return null;
-        }
+	@SuppressWarnings("unused")
+	public static class SearchProviderWithListNoType implements IResourceProvider {
 
-        @Read(version = false)
-        public Patient readPatient(@IdParam IdType theId) {
-            return null;
-        }
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return Patient.class;
+		}
 
-    }
 
-    @SuppressWarnings("unused")
-    public static class SearchProvider {
+		@Search()
+		public List<Patient> findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION) ReferenceAndListParam theIdentifier) {
+			return null;
+		}
 
-        @Search(type = Patient.class)
-        public Patient findPatient1(@Description(shortDefinition = "The patient's identifier (MRN or other card number)") @RequiredParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier) {
-            return null;
-        }
+	}
 
-        @Search(type = Patient.class)
-        public Patient findPatient2(
-                @Description(shortDefinition = "All patients linked to the given patient") @OptionalParam(name = "link", targetTypes = {Patient.class}) ReferenceAndListParam theLink) {
-            return null;
-        }
+	@SuppressWarnings("unused")
+	public static class SearchProviderWithListWithType implements IResourceProvider {
 
-    }
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return Patient.class;
+		}
 
-    @SuppressWarnings("unused")
-    public static class SearchProviderWithWhitelist {
 
-        @Search(type = Patient.class)
-        public Patient findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION, chainWhitelist = {"foo",
-                "bar"}) ReferenceAndListParam theIdentifier) {
-            return null;
-        }
+		@Search(type = Patient.class)
+		public List<Patient> findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION) ReferenceAndListParam theIdentifier) {
+			return null;
+		}
 
-    }
+	}
 
-    @SuppressWarnings("unused")
-    public static class SearchProviderWithListNoType implements IResourceProvider {
+	public static class SystemHistoryProvider {
 
-        @Override
-        public Class<? extends IBaseResource> getResourceType() {
-            return Patient.class;
-        }
+		@History
+		public List<IBaseResource> history() {
+			return null;
+		}
 
+	}
 
-        @Search()
-        public List<Patient> findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION) ReferenceAndListParam theIdentifier) {
-            return null;
-        }
+	public static class TypeHistoryProvider implements IResourceProvider {
 
-    }
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return Patient.class;
+		}
 
-    @SuppressWarnings("unused")
-    public static class SearchProviderWithListWithType implements IResourceProvider {
+		@History
+		public List<IBaseResource> history() {
+			return null;
+		}
 
-        @Override
-        public Class<? extends IBaseResource> getResourceType() {
-            return Patient.class;
-        }
+	}
 
+	@SuppressWarnings("unused")
+	public static class VreadProvider {
 
-        @Search(type = Patient.class)
-        public List<Patient> findPatient1(@Description(shortDefinition = "The organization at which this person is a patient") @RequiredParam(name = Patient.SP_ORGANIZATION) ReferenceAndListParam theIdentifier) {
-            return null;
-        }
+		@Search(type = Patient.class)
+		public Patient findPatient(@Description(shortDefinition = "The patient's identifier (MRN or other card number)") @RequiredParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier) {
+			return null;
+		}
 
-    }
+		@Read(version = true)
+		public Patient readPatient(@IdParam IdType theId) {
+			return null;
+		}
 
-    public static class SystemHistoryProvider {
+	}
 
-        @History
-        public List<IBaseResource> history() {
-            return null;
-        }
+	public static class TypeLevelOperationProvider implements IResourceProvider {
 
-    }
+		public static final String OPERATION_NAME = "op";
 
-    public static class TypeHistoryProvider implements IResourceProvider {
+		@Operation(name = OPERATION_NAME, idempotent = true)
+		public IBundleProvider op() {
+			return null;
+		}
 
-        @Override
-        public Class<? extends IBaseResource> getResourceType() {
-            return Patient.class;
-        }
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return Patient.class;
+		}
 
-        @History
-        public List<IBaseResource> history() {
-            return null;
-        }
+	}
 
-    }
+	public static class NamedQueryPlainProvider {
 
-    @SuppressWarnings("unused")
-    public static class VreadProvider {
+		public static final String QUERY_NAME = "testQuery";
+		public static final String DESCRIPTION = "A query description";
+		public static final String SP_QUANTITY = "quantity";
 
-        @Search(type = Patient.class)
-        public Patient findPatient(@Description(shortDefinition = "The patient's identifier (MRN or other card number)") @RequiredParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier) {
-            return null;
-        }
+		@Search(queryName = QUERY_NAME, type = Patient.class)
+		@Description(formalDefinition = DESCRIPTION)
+		public Bundle findAllGivenParameter(@RequiredParam(name = SP_QUANTITY) QuantityParam quantity) {
+			return null;
+		}
+	}
 
-        @Read(version = true)
-        public Patient readPatient(@IdParam IdType theId) {
-            return null;
-        }
+	public static class NamedQueryResourceProvider implements IResourceProvider {
 
-    }
+		public static final String QUERY_NAME = "testQuery";
+		public static final String SP_PARAM = "param";
 
-    public static class TypeLevelOperationProvider implements IResourceProvider {
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return Patient.class;
+		}
 
-        public static final String OPERATION_NAME = "op";
+		@Search(queryName = QUERY_NAME)
+		public Bundle findAllGivenParameter(@OptionalParam(name = SP_PARAM) StringParam param) {
+			return null;
+		}
 
-        @Operation(name = OPERATION_NAME, idempotent = true)
-        public IBundleProvider op() {
-            return null;
-        }
+	}
 
-        @Override
-        public Class<? extends IBaseResource> getResourceType() {
-            return Patient.class;
-        }
+	public static class ProfiledPatientProvider implements IResourceProvider {
 
-    }
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return PatientSubSub2.class;
+		}
 
-    public static class NamedQueryPlainProvider {
+		@Search
+		public List<PatientSubSub2> find() {
+			return null;
+		}
+	}
 
-        public static final String QUERY_NAME = "testQuery";
-        public static final String DESCRIPTION = "A query description";
-        public static final String SP_QUANTITY = "quantity";
+	public static class MultipleProfilesPatientProvider implements IResourceProvider {
 
-        @Search(queryName = QUERY_NAME)
-        @Description(formalDefinition = DESCRIPTION)
-        public Bundle findAllGivenParameter(@RequiredParam(name = SP_QUANTITY) QuantityParam quantity) {
-            return null;
-        }
-    }
+		@Override
+		public Class<? extends IBaseResource> getResourceType() {
+			return PatientSubSub.class;
+		}
 
-    public static class NamedQueryResourceProvider implements IResourceProvider {
+		@Read(type = PatientTripleSub.class)
+		public PatientTripleSub read(@IdParam IdType theId) {
+			return null;
+		}
 
-        public static final String QUERY_NAME = "testQuery";
-        public static final String SP_PARAM = "param";
+	}
 
-        @Override
-        public Class<? extends IBaseResource> getResourceType() {
-            return Patient.class;
-        }
+	@ResourceDef(id = PATIENT_SUB)
+	public static class PatientSub extends Patient {
+	}
 
-        @Search(queryName = QUERY_NAME)
-        public Bundle findAllGivenParameter(@OptionalParam(name = SP_PARAM) StringParam param) {
-            return null;
-        }
+	@ResourceDef(id = PATIENT_SUB_SUB)
+	public static class PatientSubSub extends PatientSub {
+	}
 
-    }
+	@ResourceDef(id = PATIENT_SUB_SUB_2)
+	public static class PatientSubSub2 extends PatientSub {
+	}
 
-    public static class ProfiledPatientProvider implements IResourceProvider {
+	@ResourceDef(id = PATIENT_TRIPLE_SUB)
+	public static class PatientTripleSub extends PatientSubSub {
+	}
 
-        @Override
-        public Class<? extends IBaseResource> getResourceType() {
-            return PatientSubSub2.class;
-        }
+	private static Set<String> toStrings(Collection<? extends IPrimitiveType> theType) {
+		HashSet<String> retVal = new HashSet<String>();
+		for (IPrimitiveType next : theType) {
+			retVal.add(next.getValueAsString());
+		}
+		return retVal;
+	}
 
-        @Search
-        public List<PatientSubSub2> find() {
-            return null;
-        }
-    }
-
-    public static class MultipleProfilesPatientProvider implements IResourceProvider {
-
-        @Override
-        public Class<? extends IBaseResource> getResourceType() {
-            return PatientSubSub.class;
-        }
-
-        @Read(type = PatientTripleSub.class)
-        public PatientTripleSub read(@IdParam IdType theId) {
-            return null;
-        }
-
-    }
-
-    @ResourceDef(id = PATIENT_SUB)
-    public static class PatientSub extends Patient {
-    }
-
-    @ResourceDef(id = PATIENT_SUB_SUB)
-    public static class PatientSubSub extends PatientSub {
-    }
-
-    @ResourceDef(id = PATIENT_SUB_SUB_2)
-    public static class PatientSubSub2 extends PatientSub {
-    }
-
-    @ResourceDef(id = PATIENT_TRIPLE_SUB)
-    public static class PatientTripleSub extends PatientSubSub {
-    }
-
-    private static Set<String> toStrings(Collection<? extends IPrimitiveType> theType) {
-        HashSet<String> retVal = new HashSet<String>();
-        for (IPrimitiveType next : theType) {
-            retVal.add(next.getValueAsString());
-        }
-        return retVal;
-    }
-
-    @AfterAll
-    public static void afterClassClearContext() {
-        TestUtil.clearAllStaticFieldsForUnitTest();
-    }
+	@AfterAll
+	public static void afterClassClearContext() {
+		TestUtil.clearAllStaticFieldsForUnitTest();
+	}
 
 }
diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/SchemaValidationDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/SchemaValidationDstu3Test.java
index a87dd300e02..dcc4b7287f4 100644
--- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/SchemaValidationDstu3Test.java
+++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/SchemaValidationDstu3Test.java
@@ -13,6 +13,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 
 public class SchemaValidationDstu3Test {
 
+	static {
+
+	}
+
 	private static FhirContext ourCtx = FhirContext.forDstu3();
 
 	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SchemaValidationDstu3Test.class);
diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/SchemaValidationR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/SchemaValidationR4Test.java
index f3794a85181..1d870ed32ac 100644
--- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/SchemaValidationR4Test.java
+++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/SchemaValidationR4Test.java
@@ -5,40 +5,62 @@ import ca.uhn.fhir.util.TestUtil;
 import ca.uhn.fhir.validation.FhirValidator;
 import ca.uhn.fhir.validation.ValidationResult;
 import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.xpath.XPathFactory;
+
+import java.security.CodeSource;
+import java.text.MessageFormat;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsString;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 
 public class SchemaValidationR4Test {
-
+	private static final Logger ourLog = LoggerFactory.getLogger(SchemaValidationR4Test.class);
 	private static FhirContext ourCtx = FhirContext.forDstu3();
 
-	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SchemaValidationR4Test.class);
+	@BeforeEach
+	public void before() {
+		ourLog.info(getJaxpImplementationInfo("DocumentBuilderFactory", DocumentBuilderFactory.newInstance().getClass()));
+		ourLog.info(getJaxpImplementationInfo("XPathFactory", XPathFactory.newInstance().getClass()));
+		ourLog.info(getJaxpImplementationInfo("TransformerFactory", TransformerFactory.newInstance().getClass()));
+		ourLog.info(getJaxpImplementationInfo("SAXParserFactory", SAXParserFactory.newInstance().getClass()));
+
+		// The following code can be used to force the built in schema parser to be used
+		//		System.setProperty("jaxp.debug", "1");
+		//		System.setProperty("javax.xml.validation.SchemaFactory:http://www.w3.org/2001/XMLSchema", "com.sun.org.apache.xerces.internal.jaxp.validation.XMLSchemaFactory");
+	}
 
 	/**
 	 * See #339
-	 * 
+	 * <p>
 	 * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
 	 */
 	@Test
 	public void testXxe() {
 		//@formatter:off
 		String input =
-			"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" + 
-			"<!DOCTYPE foo [  \n" + 
-			"<!ELEMENT foo ANY >\n" + 
-			"<!ENTITY xxe SYSTEM \"file:///etc/passwd\" >]>" +
-			"<Patient xmlns=\"http://hl7.org/fhir\">" +
-				"<text>" + 
-					"<status value=\"generated\"/>" +
-					"<div xmlns=\"http://www.w3.org/1999/xhtml\">TEXT &xxe; TEXT</div>\n" + 
+			"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" +
+				"<!DOCTYPE foo [  \n" +
+				"<!ELEMENT foo ANY >\n" +
+				"<!ENTITY xxe SYSTEM \"file:///etc/passwd\" >]>" +
+				"<Patient xmlns=\"http://hl7.org/fhir\">" +
+				"<text>" +
+				"<status value=\"generated\"/>" +
+				"<div xmlns=\"http://www.w3.org/1999/xhtml\">TEXT &xxe; TEXT</div>\n" +
 				"</text>" +
-				"<address>" + 
-					"<line value=\"FOO\"/>" + 
+				"<address>" +
+				"<line value=\"FOO\"/>" +
 				"</address>" +
-			"</Patient>";
+				"</Patient>";
 		//@formatter:on
 
 		FhirValidator val = ourCtx.newValidator();
@@ -49,11 +71,26 @@ public class SchemaValidationR4Test {
 		String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
 		ourLog.info(encoded);
 
+		/*
+		 * If this starts failing, check if xerces (or another simiar library) has slipped in
+		 * to the classpath as a dependency. The logs in the @Before method should include this:
+		 * DocumentBuilderFactory implementation: com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl loaded from: Java Runtime
+		 */
+
 		assertFalse(result.isSuccessful());
 		assertThat(encoded, containsString("passwd"));
 		assertThat(encoded, containsString("accessExternalDTD"));
 	}
 
+	private static String getJaxpImplementationInfo(String componentName, Class componentClass) {
+		CodeSource source = componentClass.getProtectionDomain().getCodeSource();
+		return MessageFormat.format(
+			"{0} implementation: {1} loaded from: {2}",
+			componentName,
+			componentClass.getName(),
+			source == null ? "Java Runtime" : source.getLocation());
+	}
+
 	@AfterAll
 	public static void afterClassClearContext() {
 		TestUtil.clearAllStaticFieldsForUnitTest();
diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml
index 1da19fdaf24..6be3a8a978f 100644
--- a/hapi-tinder-plugin/pom.xml
+++ b/hapi-tinder-plugin/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
@@ -58,37 +58,37 @@
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-structures-dstu3</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 		</dependency>
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 		</dependency>
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-structures-r4</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 		</dependency>
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-structures-r5</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 		</dependency>
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-validation-resources-dstu2</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 		</dependency>
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-validation-resources-dstu3</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 		</dependency>
 		<dependency>
 			<groupId>ca.uhn.hapi.fhir</groupId>
 			<artifactId>hapi-fhir-validation-resources-r4</artifactId>
-			<version>5.4.0-PRE7-SNAPSHOT</version>
+			<version>5.4.0-PRE8-SNAPSHOT</version>
 		</dependency>
 		<dependency>
 			<groupId>org.apache.velocity</groupId>
diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml
index 2f98895c12d..247b0328ab6 100644
--- a/hapi-tinder-test/pom.xml
+++ b/hapi-tinder-test/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
diff --git a/pom.xml b/pom.xml
index 109923b7ecc..38b66b8cffa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 	<groupId>ca.uhn.hapi.fhir</groupId>
 	<artifactId>hapi-fhir</artifactId>
 	<packaging>pom</packaging>
-	<version>5.4.0-PRE7-SNAPSHOT</version>
+	<version>5.4.0-PRE8-SNAPSHOT</version>
 	<name>HAPI-FHIR</name>
 	<description>An open-source implementation of the FHIR specification in Java.</description>
 	<url>https://hapifhir.io</url>
@@ -780,12 +780,13 @@
 		<jaxb_api_version>2.3.1</jaxb_api_version>
 		<jaxb_core_version>2.3.0.1</jaxb_core_version>
 		<jaxb_runtime_version>3.0.0</jaxb_runtime_version>
-		<jena_version>3.16.0</jena_version>
+		<jena_version>3.17.0</jena_version>
 		<jersey_version>3.0.0</jersey_version>
 		<!-- 9.4.17 seems to have issues -->
 		<jetty_version>9.4.39.v20210325</jetty_version>
 		<jsr305_version>3.0.2</jsr305_version>
 		<junit_version>5.7.1</junit_version>
+		<flexmark_version>0.50.40</flexmark_version>
 		<flyway_version>6.5.4</flyway_version>
 		<hibernate_version>5.4.30.Final</hibernate_version>
 		<hibernate_search_version>6.0.2.Final</hibernate_search_version>
@@ -962,6 +963,21 @@
 				<artifactId>javax.mail</artifactId>
 				<version>1.6.1</version>
 			</dependency>
+            <dependency>
+                <groupId>com.vladsch.flexmark</groupId>
+                <artifactId>flexmark</artifactId>
+                <version>${flexmark_version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.vladsch.flexmark</groupId>
+                <artifactId>flexmark-ext-tables</artifactId>
+                <version>${flexmark_version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.vladsch.flexmark</groupId>
+                <artifactId>flexmark-profile-pegdown</artifactId>
+                <version>${flexmark_version}</version>
+            </dependency>
 			<dependency>
 				<groupId>commons-cli</groupId>
 				<artifactId>commons-cli</artifactId>
@@ -1107,13 +1123,28 @@
 			<dependency>
 				<groupId>io.swagger</groupId>
 				<artifactId>swagger-annotations</artifactId>
-				<version>1.6.1</version>
+				<version>1.6.2</version>
+			</dependency>
+			<dependency>
+				<groupId>io.swagger.core.v3</groupId>
+				<artifactId>swagger-models</artifactId>
+				<version>2.1.7</version>
+			</dependency>
+			<dependency>
+				<groupId>io.swagger.core.v3</groupId>
+				<artifactId>swagger-core</artifactId>
+				<version>2.1.7</version>
 			</dependency>
 			<dependency>
 				<groupId>mysql</groupId>
 				<artifactId>mysql-connector-java</artifactId>
 				<version>8.0.20</version>
 			</dependency>
+            <dependency>
+                <groupId>net.sourceforge.htmlunit</groupId>
+                <artifactId>htmlunit</artifactId>
+                <version>2.49.1</version>
+            </dependency>
 			<dependency>
 				<groupId>net.sf.json-lib</groupId>
 				<artifactId>json-lib</artifactId>
@@ -1517,6 +1548,13 @@
 				<artifactId>javassist</artifactId>
 				<version>3.22.0-GA</version>
 			</dependency>
+			<dependency>
+				<groupId>org.junit</groupId>
+				<artifactId>junit-bom</artifactId>
+				<version>${junit_version}</version>
+				<type>pom</type>
+				<scope>import</scope>
+			</dependency>
 			<dependency>
 				<groupId>org.junit.jupiter</groupId>
 				<artifactId>junit-jupiter</artifactId>
@@ -1544,7 +1582,7 @@
 			<dependency>
 				<groupId>org.junit-pioneer</groupId>
 				<artifactId>junit-pioneer</artifactId>
-				<version>1.1.0</version>
+				<version>1.3.8</version>
 			</dependency>
 			<dependency>
 				<groupId>org.mariadb.jdbc</groupId>
@@ -1737,6 +1775,11 @@
 				<artifactId>popper.js</artifactId>
 				<version>1.16.1</version>
 			</dependency>
+			<dependency>
+				<groupId>org.webjars</groupId>
+				<artifactId>swagger-ui</artifactId>
+				<version>3.46.0</version>
+			</dependency>
 			<dependency>
 				<groupId>org.xmlunit</groupId>
 				<artifactId>xmlunit-core</artifactId>
@@ -1745,19 +1788,19 @@
 			<dependency>
 				<groupId>org.testcontainers</groupId>
 				<artifactId>testcontainers</artifactId>
-				<version>1.15.1</version>
+				<version>1.15.3</version>
 				<scope>test</scope>
 			</dependency>
 			<dependency>
 				<groupId>org.testcontainers</groupId>
 				<artifactId>elasticsearch</artifactId>
-				<version>1.15.1</version>
+				<version>1.15.3</version>
 				<scope>test</scope>
 			</dependency>
 			<dependency>
 				<groupId>org.testcontainers</groupId>
 				<artifactId>junit-jupiter</artifactId>
-				<version>1.15.1</version>
+				<version>1.15.3</version>
 				<scope>test</scope>
 			</dependency>
 			<dependency>
@@ -1923,7 +1966,7 @@
 						<runOrder>random</runOrder>
 						<argLine>@{argLine} ${surefire_jvm_args}</argLine>
 						<forkCount>1.0C</forkCount>
-						<trimStackTrace>false</trimStackTrace>
+						<trimStackTrace>true</trimStackTrace>
 					</configuration>
 				</plugin>
 				<plugin>
@@ -2101,7 +2144,7 @@
 						<dependency>
 							<groupId>com.puppycrawl.tools</groupId>
 							<artifactId>checkstyle</artifactId>
-							<version>8.29</version>
+							<version>8.41.1</version>
 						</dependency>
 					</dependencies>
 					<configuration>
@@ -2188,6 +2231,9 @@
 								<copy todir="target/site/apidocs-server-mdm">
 									<fileset dir="hapi-fhir-server-mdm/target/site/apidocs"/>
 								</copy>
+                                <copy todir="target/site/apidocs-server-openapi">
+                                    <fileset dir="hapi-fhir-server-mdm/target/site/openapi"/>
+                                </copy>
 								<copy todir="target/site/xref-jpaserver">
 									<fileset dir="hapi-fhir-jpaserver-base/target/site/xref"/>
 								</copy>
@@ -2593,6 +2639,7 @@
 				<module>hapi-fhir-client</module>
 				<module>hapi-fhir-server</module>
 				<module>hapi-fhir-server-mdm</module>
+				<module>hapi-fhir-server-openapi</module>
 				<module>hapi-fhir-converter</module>
 				<module>hapi-fhir-validation</module>
 				<!--<module>hapi-fhir-narrativegenerator</module>-->
diff --git a/restful-server-example/pom.xml b/restful-server-example/pom.xml
index ee4e9459316..dfb8121f1a7 100644
--- a/restful-server-example/pom.xml
+++ b/restful-server-example/pom.xml
@@ -8,7 +8,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	
diff --git a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml
index 757146f0f1c..5308513a49d 100644
--- a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml
+++ b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 
diff --git a/tests/hapi-fhir-base-test-mindeps-client/pom.xml b/tests/hapi-fhir-base-test-mindeps-client/pom.xml
index e295bb2a92c..4c39a242c15 100644
--- a/tests/hapi-fhir-base-test-mindeps-client/pom.xml
+++ b/tests/hapi-fhir-base-test-mindeps-client/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 
diff --git a/tests/hapi-fhir-base-test-mindeps-server/pom.xml b/tests/hapi-fhir-base-test-mindeps-server/pom.xml
index bde4aa9378f..429c414d48a 100644
--- a/tests/hapi-fhir-base-test-mindeps-server/pom.xml
+++ b/tests/hapi-fhir-base-test-mindeps-server/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>ca.uhn.hapi.fhir</groupId>
 		<artifactId>hapi-fhir</artifactId>
-		<version>5.4.0-PRE7-SNAPSHOT</version>
+		<version>5.4.0-PRE8-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>