You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2025/09/30 15:53:49 UTC

(juneau) branch master updated: OpenAPI v3 improvements.

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 1cd1481c5 OpenAPI v3 improvements.
1cd1481c5 is described below

commit 1cd1481c563aded201c4f3d6ab5a64d633b770b4
Author: James Bognar <ja...@salesforce.com>
AuthorDate: Tue Sep 30 11:53:40 2025 -0400

    OpenAPI v3 improvements.
---
 .../apache/juneau/bean/openapi/ui/OpenApiUI.java   | 416 +++++++++++++++++++++
 .../juneau/bean/openapi/ui/package-info.java       |  29 +-
 .../org/apache/juneau/bean/openapi3/Callback.java  |  87 +++--
 .../apache/juneau/bean/openapi3/Components.java    | 245 ++++++++++++
 .../apache/juneau/bean/openapi3/HeaderInfo.java    |   8 +-
 .../org/apache/juneau/bean/openapi3/Items.java     |  22 +-
 .../org/apache/juneau/bean/openapi3/OpenApi.java   | 281 ++++++++++++++
 .../juneau/bean/openapi3/OpenApiBuilder.java       | 302 +++++++++++++++
 .../org/apache/juneau/bean/openapi3/Operation.java | 309 +++++++++++++++
 .../org/apache/juneau/bean/openapi3/Parameter.java | 320 ++++++++++++++++
 .../org/apache/juneau/bean/openapi3/PathItem.java  | 303 +++++++++++++++
 .../apache/juneau/bean/openapi3/SchemaInfo.java    |  14 +-
 .../juneau/bean/openapi3/SecurityRequirement.java  |  83 ++++
 .../juneau/a/rttests/DTOs_RoundTripTest.java       |   1 -
 .../apache/juneau/{dto => bean}/atom/AtomTest.java |   3 +-
 .../juneau/{dto => bean}/html5/Html5BasicTest.java |   3 +-
 .../html5/Html5Template_ComboRoundTripTest.java    |   3 +-
 .../html5/Html5_ComboRoundTripTest.java            |   4 +-
 .../{dto => bean}/jsonschema/JsonSchema_Test.java  |   3 +-
 .../juneau/{dto => bean}/swagger/Contact_Test.java |   3 +-
 .../swagger/ExternalDocumentation_Test.java        |   3 +-
 .../{dto => bean}/swagger/HeaderInfo_Test.java     |   3 +-
 .../juneau/{dto => bean}/swagger/Info_Test.java    |   3 +-
 .../juneau/{dto => bean}/swagger/Items_Test.java   |   3 +-
 .../juneau/{dto => bean}/swagger/License_Test.java |   3 +-
 .../{dto => bean}/swagger/Operation_Test.java      |   3 +-
 .../{dto => bean}/swagger/ParameterInfo_Test.java  |   3 +-
 .../{dto => bean}/swagger/ResponseInfo_Test.java   |   3 +-
 .../{dto => bean}/swagger/SchemaInfo_Test.java     |   3 +-
 .../{dto => bean}/swagger/SecurityScheme_Test.java |   3 +-
 .../{dto => bean}/swagger/SwaggerBuilder_Test.java |   3 +-
 .../juneau/{dto => bean}/swagger/Swagger_Test.java |   4 +-
 .../juneau/{dto => bean}/swagger/Tag_Test.java     |   3 +-
 .../juneau/{dto => bean}/swagger/Xml_Test.java     |   3 +-
 34 files changed, 2368 insertions(+), 114 deletions(-)

diff --git a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi/ui/OpenApiUI.java b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi/ui/OpenApiUI.java
new file mode 100644
index 000000000..26c388229
--- /dev/null
+++ b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi/ui/OpenApiUI.java
@@ -0,0 +1,416 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you 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.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.bean.openapi.ui;
+
+import static java.util.Collections.*;
+import static org.apache.juneau.bean.html5.HtmlBuilder.*;
+import static org.apache.juneau.common.internal.Utils.set;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.bean.html5.*;
+import org.apache.juneau.bean.openapi3.*;
+import org.apache.juneau.collections.*;
+import org.apache.juneau.common.internal.*;
+import org.apache.juneau.cp.*;
+import org.apache.juneau.swap.*;
+
+/**
+ * Generates an OpenAPI-UI interface from an OpenAPI document.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='link'><a class="doclink" href="../../../../../../index.html#jd.OpenApiUi">Overview &gt; juneau-dto &gt; OpenAPI UI</a>
+ * </ul>
+ */
+public class OpenApiUI extends ObjectSwap<OpenApi,Div> {
+
+	static final FileFinder RESOURCES = FileFinder
+		.create(BeanStore.INSTANCE)
+		.cp(OpenApiUI.class, null, true)
+		.dir(",")
+		.caching(Boolean.getBoolean("RestContext.disableClasspathResourceCaching.b") ? -1 : 1_000_000)
+		.build();
+
+	private static final Set<String> STANDARD_METHODS = set("get", "put", "post", "delete", "options", "head", "patch", "trace");
+
+	/**
+	 * This UI applies to HTML requests only.
+	 */
+	@Override
+	public org.apache.juneau.MediaType[] forMediaTypes() {
+		return new org.apache.juneau.MediaType[] {org.apache.juneau.MediaType.HTML};
+	}
+
+	private static final class Session {
+		final int resolveRefsMaxDepth;
+		final OpenApi openApi;
+
+		Session(OpenApi openApi) {
+			this.openApi = openApi.copy();
+			this.resolveRefsMaxDepth = 1;
+		}
+	}
+
+	@Override
+	public Div swap(BeanSession beanSession, OpenApi openApi) throws Exception {
+
+		var s = new Session(openApi);
+
+		var css = RESOURCES.getString("files/htdocs/styles/OpenApiUI.css", null).orElse(null);
+		if (css == null)
+			css = RESOURCES.getString("OpenApiUI.css", null).orElse(null);
+
+		var outer = div(
+			style(css),
+			script("text/javascript", RESOURCES.getString("OpenApiUI.js", null).orElse(null)),
+			header(s)
+		)._class("openapi-ui");
+
+		// Operations without tags are rendered first.
+		outer.child(div()._class("tag-block tag-block-open").children(tagBlockContents(s, null)));
+
+		if (s.openApi.getTags() != null) {
+			s.openApi.getTags().forEach(x -> {
+				var tagBlock = div()._class("tag-block tag-block-open").children(
+					tagBlockSummary(x),
+					tagBlockContents(s, x)
+				);
+				outer.child(tagBlock);
+			});
+		}
+
+		if (s.openApi.getComponents() != null && s.openApi.getComponents().getSchemas() != null) {
+			var modelBlock = div()._class("tag-block").children(
+				modelsBlockSummary(),
+				modelsBlockContents(s)
+			);
+			outer.child(modelBlock);
+		}
+
+		return outer;
+	}
+
+	// Creates the informational summary before the ops.
+	private Table header(Session s) {
+		var table = table()._class("header");
+
+		var info = s.openApi.getInfo();
+		if (info != null) {
+
+			if (info.getDescription() != null)
+				table.child(tr(th("Description:"),td(toBRL(info.getDescription()))));
+
+			if (info.getVersion() != null)
+				table.child(tr(th("Version:"),td(info.getVersion())));
+
+			var c = info.getContact();
+			if (c != null) {
+				var t2 = table();
+
+				if (c.getName() != null)
+					t2.child(tr(th("Name:"),td(c.getName())));
+				if (c.getUrl() != null)
+					t2.child(tr(th("URL:"),td(a(c.getUrl(), c.getUrl()))));
+				if (c.getEmail() != null)
+					t2.child(tr(th("Email:"),td(a("mailto:"+ c.getEmail(), c.getEmail()))));
+
+				table.child(tr(th("Contact:"),td(t2)));
+			}
+
+			var l = info.getLicense();
+			if (l != null) {
+				var content = l.getName() != null ? l.getName() : l.getUrl();
+				var child = l.getUrl() != null ? a(l.getUrl(), content) : l.getName();
+				table.child(tr(th("License:"),td(child)));
+			}
+
+			ExternalDocumentation ed = s.openApi.getExternalDocs();
+			if (ed != null) {
+				var content = ed.getDescription() != null ? ed.getDescription() : ed.getUrl();
+				var child = ed.getUrl() != null ? a(ed.getUrl(), content) : ed.getDescription();
+				table.child(tr(th("Docs:"),td(child)));
+			}
+
+			if (info.getTermsOfService() != null) {
+				var tos = info.getTermsOfService();
+				var child = StringUtils.isUri(tos) ? a(tos, tos) : tos;
+				table.child(tr(th("Terms of Service:"),td(child)));
+			}
+		}
+
+		return table;
+	}
+
+	// Creates the "pet  Everything about your Pets  ext-link" header.
+	private HtmlElement tagBlockSummary(Tag t) {
+		var ed = t.getExternalDocs();
+
+		var content = ed != null && ed.getDescription() != null ? ed.getDescription() : (ed != null ? ed.getUrl() : null);
+		return div()._class("tag-block-summary").children(
+			span(t.getName())._class("name"),
+			span(toBRL(t.getDescription()))._class("description"),
+			ed != null && ed.getUrl() != null ? span(a(ed.getUrl(), content))._class("extdocs") : null
+		).onclick("toggleTagBlock(this)");
+	}
+
+	// Creates the contents under the "pet  Everything about your Pets  ext-link" header.
+	private Div tagBlockContents(Session s, Tag t) {
+		var tagBlockContents = div()._class("tag-block-contents");
+
+		if (s.openApi.getPaths() != null) {
+			s.openApi.getPaths().forEach((path, pathItem) -> {
+				// Check each HTTP method in the path item
+				if (pathItem.getGet() != null)
+					addOperationIfTagMatches(tagBlockContents, s, path, "get", pathItem.getGet(), t);
+				if (pathItem.getPut() != null)
+					addOperationIfTagMatches(tagBlockContents, s, path, "put", pathItem.getPut(), t);
+				if (pathItem.getPost() != null)
+					addOperationIfTagMatches(tagBlockContents, s, path, "post", pathItem.getPost(), t);
+				if (pathItem.getDelete() != null)
+					addOperationIfTagMatches(tagBlockContents, s, path, "delete", pathItem.getDelete(), t);
+				if (pathItem.getOptions() != null)
+					addOperationIfTagMatches(tagBlockContents, s, path, "options", pathItem.getOptions(), t);
+				if (pathItem.getHead() != null)
+					addOperationIfTagMatches(tagBlockContents, s, path, "head", pathItem.getHead(), t);
+				if (pathItem.getPatch() != null)
+					addOperationIfTagMatches(tagBlockContents, s, path, "patch", pathItem.getPatch(), t);
+				if (pathItem.getTrace() != null)
+					addOperationIfTagMatches(tagBlockContents, s, path, "trace", pathItem.getTrace(), t);
+			});
+		}
+
+		return tagBlockContents;
+	}
+
+	private void addOperationIfTagMatches(Div tagBlockContents, Session s, String path, String method, Operation op, Tag t) {
+		if ((t == null && (op.getTags() == null || op.getTags().isEmpty())) || 
+			(t != null && op.getTags() != null && op.getTags().contains(t.getName()))) {
+			tagBlockContents.child(opBlock(s, path, method, op));
+		}
+	}
+
+	private Div opBlock(Session s, String path, String opName, Operation op) {
+
+		var opClass = op.getDeprecated() != null && op.getDeprecated() ? "deprecated" : opName.toLowerCase();
+		if (!(op.getDeprecated() != null && op.getDeprecated()) && !STANDARD_METHODS.contains(opClass))
+			opClass = "other";
+
+		return div()._class("op-block op-block-closed " + opClass).children(
+			opBlockSummary(path, opName, op),
+			div(tableContainer(s, op))._class("op-block-contents")
+		);
+	}
+
+	private HtmlElement opBlockSummary(String path, String opName, Operation op) {
+		return div()._class("op-block-summary").children(
+			span(opName.toUpperCase())._class("method-button"),
+			span(path)._class("path"),
+			op.getSummary() != null ? span(op.getSummary())._class("summary") : null
+		).onclick("toggleOpBlock(this)");
+	}
+
+	private Div tableContainer(Session s, Operation op) {
+		var tableContainer = div()._class("table-container");
+
+		if (op.getDescription() != null)
+			tableContainer.child(div(toBRL(op.getDescription()))._class("op-block-description"));
+
+		if (op.getParameters() != null) {
+			tableContainer.child(div(h4("Parameters")._class("title"))._class("op-block-section-header"));
+
+			var parameters = table(tr(th("Name")._class("parameter-key"), th("Description")._class("parameter-key")))._class("parameters");
+
+			op.getParameters().forEach(x -> {
+				var piName = x.getName();
+				var required = x.getRequired() != null && x.getRequired();
+
+				var parameterKey = td(
+					div(piName)._class("name" + (required ? " required" : "")),
+					required ? div("required")._class("requiredlabel") : null,
+					x.getSchema() != null ? div(x.getSchema().getType())._class("type") : null,
+					div('(' + x.getIn() + ')')._class("in")
+				)._class("parameter-key");
+
+				var parameterValue = td(
+					div(toBRL(x.getDescription()))._class("description"),
+					examples(s, x)
+				)._class("parameter-value");
+
+				parameters.child(tr(parameterKey, parameterValue));
+			});
+
+			tableContainer.child(parameters);
+		}
+
+		if (op.getResponses() != null) {
+			tableContainer.child(div(h4("Responses")._class("title"))._class("op-block-section-header"));
+
+			var responses = table(tr(th("Code")._class("response-key"), th("Description")._class("response-key")))._class("responses");
+			tableContainer.child(responses);
+
+			op.getResponses().forEach((k,v) -> {
+				var code = td(k)._class("response-key");
+
+				var codeValue = td(
+					div(toBRL(v.getDescription()))._class("description"),
+					examples(s, v),
+					headers(v)
+				)._class("response-value");
+
+				responses.child(tr(code, codeValue));
+			});
+		}
+
+		return tableContainer;
+	}
+
+	private Div headers(Response ri) {
+		if (ri.getHeaders() == null)
+			return null;
+
+		var sectionTable = table(tr(th("Name"),th("Description"),th("Schema")))._class("section-table");
+
+		var headers = div(
+			div("Headers:")._class("section-name"),
+			sectionTable
+		)._class("headers");
+
+		ri.getHeaders().forEach((k,v) ->
+			sectionTable.child(
+				tr(
+					td(k)._class("name"),
+					td(toBRL(v.getDescription()))._class("description"),
+					td(v.asMap().keepAll("type","format","items","collectionFormat","default","maximum","exclusiveMaximum","minimum","exclusiveMinimum","maxLength","minLength","pattern","maxItems","minItems","uniqueItems","enum","multipleOf"))
+				)
+			)
+		);
+
+		return headers;
+	}
+
+	private Div examples(Session s, Parameter pi) {
+		var m = new JsonMap();
+
+		try {
+			var si = pi.getSchema();
+			if (si != null)
+				m.put("model", si.copy().resolveRefs(s.openApi, new ArrayDeque<>(), s.resolveRefsMaxDepth));
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+		if (m.isEmpty())
+			return null;
+
+		return examplesDiv(m);
+	}
+
+	private Div examples(Session s, Response ri) {
+		var m = new JsonMap();
+		try {
+			var content = ri.getContent();
+			if (content != null) {
+				// For OpenAPI 3.0, content is a map of media types to MediaType objects
+				content.forEach((mediaType, mediaTypeObj) -> {
+					if (mediaTypeObj.getSchema() != null) {
+						try {
+							var schema = mediaTypeObj.getSchema().copy().resolveRefs(s.openApi, new ArrayDeque<>(), s.resolveRefsMaxDepth);
+							m.put(mediaType, schema);
+						} catch (Exception e) {
+							e.printStackTrace();
+						}
+					}
+				});
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+		if (m.isEmpty())
+			return null;
+
+		return examplesDiv(m);
+	}
+
+	private Div examplesDiv(JsonMap m) {
+		if (m.isEmpty())
+			return null;
+
+		Select select = null;
+		if (m.size() > 1) {
+			select = select().onchange("selectExample(this)")._class("example-select");
+		}
+
+		var div = div(select)._class("examples");
+
+		if (select != null)
+			select.child(option("model","model"));
+		div.child(div(m.remove("model"))._class("model active").attr("data-name", "model"));
+
+		var select2 = select;
+		m.forEach((k,v) -> {
+			if (select2 != null)
+				select2.child(option(k, k));
+			div.child(div(v.toString().replace("\\n", "\n"))._class("example").attr("data-name", k));
+		});
+
+		return div;
+	}
+
+	// Creates the "Model" header.
+	private HtmlElement modelsBlockSummary() {
+		return div()._class("tag-block-summary").children(span("Models")._class("name")).onclick("toggleTagBlock(this)");
+	}
+
+	// Creates the contents under the "Model" header.
+	private Div modelsBlockContents(Session s) {
+		var modelBlockContents = div()._class("tag-block-contents");
+		if (s.openApi.getComponents() != null && s.openApi.getComponents().getSchemas() != null) {
+			s.openApi.getComponents().getSchemas().forEach((k,v) -> modelBlockContents.child(modelBlock(k,v)));
+		}
+		return modelBlockContents;
+	}
+
+	private Div modelBlock(String modelName, SchemaInfo model) {
+		return div()._class("op-block op-block-closed model").children(
+			modelBlockSummary(modelName, model),
+			div(model)._class("op-block-contents")
+		);
+	}
+
+	private HtmlElement modelBlockSummary(String modelName, SchemaInfo model) {
+		return div()._class("op-block-summary").children(
+			span(modelName)._class("method-button"),
+			model.getDescription() != null ? span(toBRL(model.getDescription()))._class("summary") : null
+		).onclick("toggleOpBlock(this)");
+	}
+
+	/**
+	 * Replaces newlines with <br> elements.
+	 */
+	private static List<Object> toBRL(String s) {
+		if (s == null)
+			return null;  // NOSONAR - Intentionally returning null.
+		if (s.indexOf(',') == -1)
+			return singletonList(s);
+		var l = Utils.list();
+		var sa = s.split("\n");
+		for (var i = 0; i < sa.length; i++) {
+			if (i > 0)
+				l.add(br());
+			l.add(sa[i]);
+		}
+		return l;
+	}
+}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/html5/Html5BasicTest.java b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi/ui/package-info.java
similarity index 67%
copy from juneau-utest/src/test/java/org/apache/juneau/dto/html5/Html5BasicTest.java
copy to juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi/ui/package-info.java
index 0d2ac4f34..2cb324eb7 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/html5/Html5BasicTest.java
+++ b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi/ui/package-info.java
@@ -10,29 +10,8 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.html5;
 
-import static org.apache.juneau.bean.html5.HtmlBuilder.*;
-import static org.junit.jupiter.api.Assertions.*;
-
-import org.apache.juneau.*;
-import org.apache.juneau.bean.html5.*;
-import org.junit.jupiter.api.*;
-
-class Html5BasicTest extends SimpleTestBase {
-
-	@Test void a01_toString() {
-		Form f = form("bar",
-			fieldset(
-				legend("foo:"),
-				"Name:", input("text"), br(),
-				"Email:", input("text"), br(),
-				"X:", keygen().name("X"),
-				label("label")._for("Name")
-			)
-		);
-
-		var r = f.toString();
-		assertEquals("<form action='bar'><fieldset><legend>foo:</legend>Name:<input type='text'/><br/>Email:<input type='text'/><br/>X:<keygen name='X'/><label for='Name'>label</label></fieldset></form>", r);
-	}
-}
\ No newline at end of file
+/**
+ * OpenAPI UI Generator
+ */
+package org.apache.juneau.bean.openapi.ui;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/DTOs_RoundTripTest.java b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Callback.java
old mode 100755
new mode 100644
similarity index 50%
copy from juneau-utest/src/test/java/org/apache/juneau/a/rttests/DTOs_RoundTripTest.java
copy to juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Callback.java
index 416628883..d6031e8e6
--- a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/DTOs_RoundTripTest.java
+++ b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Callback.java
@@ -10,35 +10,74 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.a.rttests;
+package org.apache.juneau.bean.openapi3;
 
-import static org.apache.juneau.TestUtils.*;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.apache.juneau.internal.CollectionUtils.*;
 
-import org.apache.juneau.bean.jsonschema.*;
-import org.apache.juneau.dto.jsonschema.*;
-import org.junit.jupiter.params.*;
-import org.junit.jupiter.params.provider.*;
+import java.util.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
 
 /**
- * Tests designed to serialize and parse objects to make sure we end up
- * with the same objects for all serializers and parsers.
+ * A map of possible out-of-band callbacks related to the parent operation.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='link'><a class="doclink" href="../../../../../index.html#jrs.OpenApi">Overview &gt; juneau-rest-server &gt; OpenAPI</a>
+ * </ul>
  */
-class DTOs_RoundTripTest extends RoundTripTest_Base {
-
-	@ParameterizedTest
-	@MethodSource("testers")
-	void a01_jsonSchema1(RoundTrip_Tester t) throws Exception {
-		var x1 = JsonSchema_Test.getTest1();
-		var x2 = t.roundTrip(x1, JsonSchema.class);
-		assertEquals(json(x2), json(x1));
+@Bean(properties="*")
+@FluentSetters
+public class Callback extends OpenApiElement {
+
+	private Map<String,PathItem> callbacks;
+
+	/**
+	 * Default constructor.
+	 */
+	public Callback() {}
+
+	/**
+	 * Copy constructor.
+	 *
+	 * @param copyFrom The object to copy.
+	 */
+	public Callback(Callback copyFrom) {
+		super(copyFrom);
+		this.callbacks = copyOf(copyFrom.callbacks);
+	}
+
+	/**
+	 * Returns the callbacks map.
+	 *
+	 * @return The callbacks map.
+	 */
+	public Map<String,PathItem> getCallbacks() {
+		return callbacks;
+	}
+
+	/**
+	 * Sets the callbacks map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Callback setCallbacks(Map<String,PathItem> value) {
+		this.callbacks = value;
+		return this;
 	}
 
-	@ParameterizedTest
-	@MethodSource("testers")
-	void a02_jsonSchema2(RoundTrip_Tester t) throws Exception {
-		var x1 = JsonSchema_Test.getTest2();
-		var x2 = t.roundTrip(x1, JsonSchema.class);
-		assertEquals(json(x2), json(x1));
+	/**
+	 * Adds a callback.
+	 *
+	 * @param expression The callback expression.
+	 * @param pathItem The path item for the callback.
+	 * @return This object.
+	 */
+	public Callback addCallback(String expression, PathItem pathItem) {
+		if (callbacks == null)
+			callbacks = new LinkedHashMap<>();
+		callbacks.put(expression, pathItem);
+		return this;
 	}
-}
\ No newline at end of file
+}
diff --git a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Components.java b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Components.java
new file mode 100644
index 000000000..2e762647f
--- /dev/null
+++ b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Components.java
@@ -0,0 +1,245 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you 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.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.bean.openapi3;
+
+import static org.apache.juneau.internal.CollectionUtils.*;
+
+import java.util.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Holds a set of reusable objects for different aspects of the OAS.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='link'><a class="doclink" href="../../../../../index.html#jrs.OpenApi">Overview &gt; juneau-rest-server &gt; OpenAPI</a>
+ * </ul>
+ */
+@Bean(properties="schemas,responses,parameters,examples,requestBodies,headers,securitySchemes,links,callbacks,*")
+@FluentSetters
+public class Components extends OpenApiElement {
+
+	private Map<String,SchemaInfo> schemas;
+	private Map<String,Response> responses;
+	private Map<String,Parameter> parameters;
+	private Map<String,Example> examples;
+	private Map<String,RequestBodyInfo> requestBodies;
+	private Map<String,HeaderInfo> headers;
+	private Map<String,SecuritySchemeInfo> securitySchemes;
+	private Map<String,Link> links;
+	private Map<String,Callback> callbacks;
+
+	/**
+	 * Default constructor.
+	 */
+	public Components() {}
+
+	/**
+	 * Copy constructor.
+	 *
+	 * @param copyFrom The object to copy.
+	 */
+	public Components(Components copyFrom) {
+		super(copyFrom);
+		this.schemas = copyOf(copyFrom.schemas);
+		this.responses = copyOf(copyFrom.responses);
+		this.parameters = copyOf(copyFrom.parameters);
+		this.examples = copyOf(copyFrom.examples);
+		this.requestBodies = copyOf(copyFrom.requestBodies);
+		this.headers = copyOf(copyFrom.headers);
+		this.securitySchemes = copyOf(copyFrom.securitySchemes);
+		this.links = copyOf(copyFrom.links);
+		this.callbacks = copyOf(copyFrom.callbacks);
+	}
+
+	/**
+	 * Returns the schemas map.
+	 *
+	 * @return The schemas map.
+	 */
+	public Map<String,SchemaInfo> getSchemas() {
+		return schemas;
+	}
+
+	/**
+	 * Sets the schemas map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Components setSchemas(Map<String,SchemaInfo> value) {
+		this.schemas = value;
+		return this;
+	}
+
+	/**
+	 * Returns the responses map.
+	 *
+	 * @return The responses map.
+	 */
+	public Map<String,Response> getResponses() {
+		return responses;
+	}
+
+	/**
+	 * Sets the responses map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Components setResponses(Map<String,Response> value) {
+		this.responses = value;
+		return this;
+	}
+
+	/**
+	 * Returns the parameters map.
+	 *
+	 * @return The parameters map.
+	 */
+	public Map<String,Parameter> getParameters() {
+		return parameters;
+	}
+
+	/**
+	 * Sets the parameters map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Components setParameters(Map<String,Parameter> value) {
+		this.parameters = value;
+		return this;
+	}
+
+	/**
+	 * Returns the examples map.
+	 *
+	 * @return The examples map.
+	 */
+	public Map<String,Example> getExamples() {
+		return examples;
+	}
+
+	/**
+	 * Sets the examples map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Components setExamples(Map<String,Example> value) {
+		this.examples = value;
+		return this;
+	}
+
+	/**
+	 * Returns the request bodies map.
+	 *
+	 * @return The request bodies map.
+	 */
+	public Map<String,RequestBodyInfo> getRequestBodies() {
+		return requestBodies;
+	}
+
+	/**
+	 * Sets the request bodies map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Components setRequestBodies(Map<String,RequestBodyInfo> value) {
+		this.requestBodies = value;
+		return this;
+	}
+
+	/**
+	 * Returns the headers map.
+	 *
+	 * @return The headers map.
+	 */
+	public Map<String,HeaderInfo> getHeaders() {
+		return headers;
+	}
+
+	/**
+	 * Sets the headers map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Components setHeaders(Map<String,HeaderInfo> value) {
+		this.headers = value;
+		return this;
+	}
+
+	/**
+	 * Returns the security schemes map.
+	 *
+	 * @return The security schemes map.
+	 */
+	public Map<String,SecuritySchemeInfo> getSecuritySchemes() {
+		return securitySchemes;
+	}
+
+	/**
+	 * Sets the security schemes map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Components setSecuritySchemes(Map<String,SecuritySchemeInfo> value) {
+		this.securitySchemes = value;
+		return this;
+	}
+
+	/**
+	 * Returns the links map.
+	 *
+	 * @return The links map.
+	 */
+	public Map<String,Link> getLinks() {
+		return links;
+	}
+
+	/**
+	 * Sets the links map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Components setLinks(Map<String,Link> value) {
+		this.links = value;
+		return this;
+	}
+
+	/**
+	 * Returns the callbacks map.
+	 *
+	 * @return The callbacks map.
+	 */
+	public Map<String,Callback> getCallbacks() {
+		return callbacks;
+	}
+
+	/**
+	 * Sets the callbacks map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Components setCallbacks(Map<String,Callback> value) {
+		this.callbacks = value;
+		return this;
+	}
+}
diff --git a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/HeaderInfo.java b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/HeaderInfo.java
index 0c57e985c..cede8cdfa 100644
--- a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/HeaderInfo.java
+++ b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/HeaderInfo.java
@@ -18,7 +18,6 @@ import static org.apache.juneau.internal.ConverterUtils.*;
 import java.util.*;
 
 import org.apache.juneau.annotation.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.internal.*;
 
@@ -449,7 +448,7 @@ public class HeaderInfo extends OpenApiElement {
 	/**
 	 * Resolves any <js>"$ref"</js> attributes in this element.
 	 *
-	 * @param swagger The swagger document containing the definitions.
+	 * @param openApi The swagger document containing the definitions.
 	 * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops.
 	 * @param maxDepth
 	 * 	The maximum depth to resolve references.
@@ -459,13 +458,14 @@ public class HeaderInfo extends OpenApiElement {
 	 * 	This object with references resolved.
 	 * 	<br>May or may not be the same object.
 	 */
-	public HeaderInfo resolveRefs(Swagger swagger, Deque<String> refStack, int maxDepth) {
+	public HeaderInfo resolveRefs(OpenApi openApi, Deque<String> refStack, int maxDepth) {
 
 		if (ref != null) {
 			if (refStack.contains(ref) || refStack.size() >= maxDepth)
 				return this;
 			refStack.addLast(ref);
-			var r = swagger.findRef(ref, HeaderInfo.class).resolveRefs(swagger, refStack, maxDepth);
+			var r = openApi.findRef(ref, HeaderInfo.class);
+			r = r.resolveRefs(openApi, refStack, maxDepth);
 			refStack.removeLast();
 			return r;
 		}
diff --git a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Items.java b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Items.java
index be5fc1c84..a1b33a0c2 100644
--- a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Items.java
+++ b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Items.java
@@ -21,7 +21,6 @@ import java.util.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.collections.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.internal.*;
@@ -745,7 +744,7 @@ public class Items extends OpenApiElement {
 	/**
 	 * Resolves any <js>"$ref"</js> attributes in this element.
 	 *
-	 * @param swagger The swagger document containing the definitions.
+	 * @param openApi The swagger document containing the definitions.
 	 * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops.
 	 * @param maxDepth
 	 * 	The maximum depth to resolve references.
@@ -755,21 +754,22 @@ public class Items extends OpenApiElement {
 	 * 	This object with references resolved.
 	 * 	<br>May or may not be the same object.
 	 */
-	public Items resolveRefs(Swagger swagger, Deque<String> refStack, int maxDepth) {
+	public Items resolveRefs(OpenApi openApi, Deque<String> refStack, int maxDepth) {
 
 		if (ref != null) {
 			if (refStack.contains(ref) || refStack.size() >= maxDepth)
 				return this;
 			refStack.addLast(ref);
-			var r = swagger.findRef(ref, Items.class).resolveRefs(swagger, refStack, maxDepth);
+			var r = openApi.findRef(ref, Items.class);
+			r = r.resolveRefs(openApi, refStack, maxDepth);
 			refStack.removeLast();
 			return r;
 		}
 
-		set("properties", resolveRefs(get("properties"), swagger, refStack, maxDepth));
+		set("properties", resolveRefs(get("properties"), openApi, refStack, maxDepth));
 
 		if (items != null)
-			items = items.resolveRefs(swagger, refStack, maxDepth);
+			items = items.resolveRefs(openApi, refStack, maxDepth);
 
 		set("example", null);
 
@@ -777,7 +777,7 @@ public class Items extends OpenApiElement {
 	}
 
 	/* Resolve references in extra attributes */
-	private Object resolveRefs(Object o, Swagger swagger, Deque<String> refStack, int maxDepth) {
+	private Object resolveRefs(Object o, OpenApi openApi, Deque<String> refStack, int maxDepth) {
 		if (o instanceof JsonMap om) {
 			var ref2 = om.get("$ref");
 			if (ref2 instanceof CharSequence) {
@@ -785,17 +785,17 @@ public class Items extends OpenApiElement {
 				if (refStack.contains(sref) || refStack.size() >= maxDepth)
 					return o;
 				refStack.addLast(sref);
-				var o2 = swagger.findRef(sref, Object.class);
-				o2 = resolveRefs(o2, swagger, refStack, maxDepth);
+				var o2 = openApi.findRef(sref, Object.class);
+				o2 = resolveRefs(o2, openApi, refStack, maxDepth);
 				refStack.removeLast();
 				return o2;
 			}
 			for (var e : om.entrySet())
-				e.setValue(resolveRefs(e.getValue(), swagger, refStack, maxDepth));
+				e.setValue(resolveRefs(e.getValue(), openApi, refStack, maxDepth));
 		}
 		if (o instanceof JsonList x)
 			for (var li = x.listIterator(); li.hasNext();)
-				li.set(resolveRefs(li.next(), swagger, refStack, maxDepth));
+				li.set(resolveRefs(li.next(), openApi, refStack, maxDepth));
 		return o;
 	}
 }
\ No newline at end of file
diff --git a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/OpenApi.java b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/OpenApi.java
new file mode 100644
index 000000000..628816d98
--- /dev/null
+++ b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/OpenApi.java
@@ -0,0 +1,281 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you 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.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.bean.openapi3;
+
+import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.internal.CollectionUtils.*;
+
+import java.util.*;
+import java.util.TreeMap;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.common.internal.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.objecttools.*;
+
+/**
+ * This is the root document object for the OpenAPI specification.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='link'><a class="doclink" href="../../../../../index.html#jrs.OpenApi">Overview &gt; juneau-rest-server &gt; OpenAPI</a>
+ * </ul>
+ */
+@Bean(properties="openapi,info,servers,paths,components,security,tags,externalDocs,*")
+@FluentSetters
+public class OpenApi extends OpenApiElement {
+
+	/** Represents a null OpenAPI document */
+	public static final OpenApi NULL = new OpenApi();
+
+	private static final Comparator<String> PATH_COMPARATOR = (o1, o2) -> o1.replace('{', '@').compareTo(o2.replace('{', '@'));
+
+	private String openapi = "3.0.0";
+	private Info info;
+	private List<Server> servers;
+	private Map<String,PathItem> paths;
+	private Components components;
+	private List<SecurityRequirement> security;
+	private List<Tag> tags;
+	private ExternalDocumentation externalDocs;
+
+	/**
+	 * Default constructor.
+	 */
+	public OpenApi() {}
+
+	/**
+	 * Copy constructor.
+	 *
+	 * @param copyFrom The object to copy.
+	 */
+	public OpenApi(OpenApi copyFrom) {
+		super(copyFrom);
+		this.openapi = copyFrom.openapi;
+		this.info = copyFrom.info;
+		this.servers = copyOf(copyFrom.servers);
+		this.paths = copyOf(copyFrom.paths);
+		this.components = copyFrom.components;
+		this.security = copyOf(copyFrom.security);
+		this.tags = copyOf(copyFrom.tags);
+		this.externalDocs = copyFrom.externalDocs;
+	}
+
+	/**
+	 * Make a deep copy of this object.
+	 *
+	 * @return A deep copy of this object.
+	 */
+	public OpenApi copy() {
+		return new OpenApi(this);
+	}
+
+	/**
+	 * Returns the OpenAPI version.
+	 *
+	 * @return The OpenAPI version.
+	 */
+	public String getOpenapi() {
+		return openapi;
+	}
+
+	/**
+	 * Sets the OpenAPI version.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public OpenApi setOpenapi(String value) {
+		this.openapi = value;
+		return this;
+	}
+
+	/**
+	 * Returns the info object.
+	 *
+	 * @return The info object.
+	 */
+	public Info getInfo() {
+		return info;
+	}
+
+	/**
+	 * Sets the info object.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public OpenApi setInfo(Info value) {
+		this.info = value;
+		return this;
+	}
+
+	/**
+	 * Returns the servers list.
+	 *
+	 * @return The servers list.
+	 */
+	public List<Server> getServers() {
+		return servers;
+	}
+
+	/**
+	 * Sets the servers list.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public OpenApi setServers(List<Server> value) {
+		this.servers = value;
+		return this;
+	}
+
+	/**
+	 * Returns the paths map.
+	 *
+	 * @return The paths map.
+	 */
+	public Map<String,PathItem> getPaths() {
+		return paths;
+	}
+
+	/**
+	 * Sets the paths map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public OpenApi setPaths(Map<String,PathItem> value) {
+		this.paths = mapBuilder(String.class,PathItem.class).sparse().sorted(PATH_COMPARATOR).addAll(value).build();
+		return this;
+	}
+
+	/**
+	 * Adds a path to this OpenAPI document.
+	 *
+	 * @param path The path string.
+	 * @param pathItem The path item.
+	 * @return This object.
+	 */
+	public OpenApi addPath(String path, PathItem pathItem) {
+		if (paths == null)
+			paths = new TreeMap<>(PATH_COMPARATOR);
+		getPaths().put(path, pathItem);
+		return this;
+	}
+
+	/**
+	 * Returns the components object.
+	 *
+	 * @return The components object.
+	 */
+	public Components getComponents() {
+		return components;
+	}
+
+	/**
+	 * Sets the components object.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public OpenApi setComponents(Components value) {
+		this.components = value;
+		return this;
+	}
+
+	/**
+	 * Returns the security requirements list.
+	 *
+	 * @return The security requirements list.
+	 */
+	public List<SecurityRequirement> getSecurity() {
+		return security;
+	}
+
+	/**
+	 * Sets the security requirements list.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public OpenApi setSecurity(List<SecurityRequirement> value) {
+		this.security = value;
+		return this;
+	}
+
+	/**
+	 * Returns the tags list.
+	 *
+	 * @return The tags list.
+	 */
+	public List<Tag> getTags() {
+		return tags;
+	}
+
+	/**
+	 * Sets the tags list.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public OpenApi setTags(List<Tag> value) {
+		this.tags = value;
+		return this;
+	}
+
+	/**
+	 * Returns the external documentation.
+	 *
+	 * @return The external documentation.
+	 */
+	public ExternalDocumentation getExternalDocs() {
+		return externalDocs;
+	}
+
+	/**
+	 * Sets the external documentation.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public OpenApi setExternalDocs(ExternalDocumentation value) {
+		this.externalDocs = value;
+		return this;
+	}
+
+	/**
+	 * Finds a reference within this OpenAPI document.
+	 *
+	 * @param ref The reference string (e.g., <js>"#/components/schemas/User"</js>).
+	 * @param c The expected class type.
+	 * @return The referenced node, or <jk>null</jk> if the ref was <jk>null</jk> or empty or not found.
+	 */
+	public <T> T findRef(String ref, Class<T> c) {
+		if (Utils.isEmpty(ref))
+			return null;
+		if (! ref.startsWith("#/"))
+			throw new BasicRuntimeException("Unsupported reference:  ''{0}''", ref);
+		try {
+			return new ObjectRest(this).get(ref.substring(1), c);
+		} catch (Exception e) {
+			throw new BeanRuntimeException(e, c, "Reference ''{0}'' could not be converted to type ''{1}''.", ref, className(c));
+		}
+	}
+
+	@Override
+	public String toString() {
+		return JsonSerializer.DEFAULT_READABLE.toString(this);
+	}
+}
diff --git a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/OpenApiBuilder.java b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/OpenApiBuilder.java
new file mode 100644
index 000000000..3b5c0da44
--- /dev/null
+++ b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/OpenApiBuilder.java
@@ -0,0 +1,302 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you 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.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.bean.openapi3;
+
+import java.net.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.common.internal.*;
+
+/**
+ * Various useful static methods for creating OpenAPI elements.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='link'><a class="doclink" href="../../../../../index.html#jrs.OpenApi">Overview &gt; juneau-rest-server &gt; OpenAPI</a>
+ * </ul>
+ */
+public class OpenApiBuilder {
+
+	/**
+	 * Constructor.
+	 */
+	protected OpenApiBuilder() {}
+
+	/**
+	 * Creates an empty {@link Contact} element.
+	 *
+	 * @return The new element.
+	 */
+	public static final Contact contact() {
+		return new Contact();
+	}
+
+	/**
+	 * Creates an {@link Contact} element with the specified {@link Contact#setName(String) name} attribute.
+	 *
+	 * @param name The {@link Contact#setName(String) name} attribute.
+	 * @return The new element.
+	 */
+	public static final Contact contact(String name) {
+		return contact().setName(name);
+	}
+
+	/**
+	 * Creates an {@link Contact} element with the specified {@link Contact#setName(String) name}, {@link Contact#setUrl(URI) url},
+	 * and {@link Contact#setEmail(String) email} attributes.
+	 *
+	 * @param name The {@link Contact#setName(String) name} attribute.
+	 * @param url
+	 * 	The {@link Contact#setUrl(URI) url} attribute.
+	 * 	<br>The value can be of any of the following types: {@link URI}, {@link URL}, {@link String}.
+	 * 	<br>Strings must be valid URIs.
+	 * 	<br>URIs defined by {@link UriResolver} can be used for values.
+	 * @param email The {@link Contact#setEmail(String) email} attribute.
+	 * @return The new element.
+	 */
+	public static final Contact contact(String name, Object url, String email) {
+		return contact().setName(name).setUrl(StringUtils.toURI(url)).setEmail(email);
+	}
+
+	/**
+	 * Creates an empty {@link ExternalDocumentation} element.
+	 *
+	 * @return The new element.
+	 */
+	public static final ExternalDocumentation externalDocumentation() {
+		return new ExternalDocumentation();
+	}
+
+	/**
+	 * Creates an {@link ExternalDocumentation} element with the specified {@link ExternalDocumentation#setUrl(URI) url}
+	 * attribute.
+	 *
+	 * @param url
+	 * 	The {@link ExternalDocumentation#setUrl(URI) url} attribute.
+	 * 	<br>The value can be of any of the following types: {@link URI}, {@link URL}, {@link String}.
+	 * 	<br>Strings must be valid URIs.
+	 * 	<br>URIs defined by {@link UriResolver} can be used for values.
+	 * @return The new element.
+	 */
+	public static final ExternalDocumentation externalDocumentation(Object url) {
+		return externalDocumentation().setUrl(StringUtils.toURI(url));
+	}
+
+	/**
+	 * Creates an {@link ExternalDocumentation} element with the specified {@link ExternalDocumentation#setUrl(URI) url}
+	 * and {@link ExternalDocumentation#setDescription(String) description} attributes.
+	 *
+	 * @param url
+	 * 	The {@link ExternalDocumentation#setUrl(URI) url} attribute.
+	 * 	<br>The value can be of any of the following types: {@link URI}, {@link URL}, {@link String}.
+	 * 	<br>Strings must be valid URIs.
+	 * 	<br>URIs defined by {@link UriResolver} can be used for values.
+	 * @param description The {@link ExternalDocumentation#setDescription(String) description} attribute.
+	 * @return The new element.
+	 */
+	public static final ExternalDocumentation externalDocumentation(Object url, String description) {
+		return externalDocumentation().setUrl(StringUtils.toURI(url)).setDescription(description);
+	}
+
+	/**
+	 * Creates an empty {@link HeaderInfo} element.
+	 *
+	 * @return The new element.
+	 */
+	public static final HeaderInfo headerInfo() {
+		return new HeaderInfo();
+	}
+
+	/**
+	 * Creates an {@link HeaderInfo} element with the specified {@link HeaderInfo#setSchema(SchemaInfo) schema} attribute.
+	 *
+	 * @param schema The {@link HeaderInfo#setSchema(SchemaInfo) schema} attribute.
+	 * @return The new element.
+	 */
+	public static final HeaderInfo headerInfo(SchemaInfo schema) {
+		return headerInfo().setSchema(schema);
+	}
+
+	/**
+	 * Creates an empty {@link Info} element.
+	 *
+	 * @return The new element.
+	 */
+	public static final Info info() {
+		return new Info();
+	}
+
+	/**
+	 * Creates an {@link Info} element with the specified {@link Info#setTitle(String) title} and {@link Info#setVersion(String) version}
+	 * attributes.
+	 *
+	 * @param title The {@link Info#setTitle(String) title} attribute.
+	 * @param version The {@link Info#setVersion(String) version} attribute.
+	 * @return The new element.
+	 */
+	public static final Info info(String title, String version) {
+		return info().setTitle(title).setVersion(version);
+	}
+
+	/**
+	 * Creates an empty {@link Items} element.
+	 *
+	 * @return The new element.
+	 */
+	public static final Items items() {
+		return new Items();
+	}
+
+	/**
+	 * Creates an {@link Items} element with the specified {@link Items#setType(String) type} attribute.
+	 *
+	 * @param type The {@link Items#setType(String) type} attribute.
+	 * @return The new element.
+	 */
+	public static final Items items(String type) {
+		return items().setType(type);
+	}
+
+	/**
+	 * Creates an empty {@link License} element.
+	 *
+	 * @return The new element.
+	 */
+	public static final License license() {
+		return new License();
+	}
+
+	/**
+	 * Creates an {@link License} element with the specified {@link License#setName(String) name} attribute.
+	 *
+	 * @param name The {@link License#setName(String) name} attribute.
+	 * @return The new element.
+	 */
+	public static final License license(String name) {
+		return license().setName(name);
+	}
+
+	/**
+	 * Creates an {@link License} element with the specified {@link License#setName(String) name} and {@link License#setUrl(URI) url} attributes.
+	 *
+	 * @param name The {@link License#setName(String) name} attribute.
+	 * @param url The {@link License#setUrl(URI) url} attribute.
+	 * @return The new element.
+	 */
+	public static final License license(String name, URI url) {
+		return license().setName(name).setUrl(url);
+	}
+
+	/**
+	 * Creates an empty {@link OpenApi} element.
+	 *
+	 * @return The new element.
+	 */
+	public static final OpenApi openApi() {
+		return new OpenApi();
+	}
+
+	/**
+	 * Creates an {@link OpenApi} element with the specified {@link OpenApi#setInfo(Info) info} attribute.
+	 *
+	 * @param info The {@link OpenApi#setInfo(Info) info} attribute.
+	 * @return The new element.
+	 */
+	public static final OpenApi openApi(Info info) {
+		return openApi().setInfo(info);
+	}
+
+	/**
+	 * Creates an empty {@link SchemaInfo} element.
+	 *
+	 * @return The new element.
+	 */
+	public static final SchemaInfo schemaInfo() {
+		return new SchemaInfo();
+	}
+
+	/**
+	 * Creates an {@link SchemaInfo} element with the specified {@link SchemaInfo#setType(String) type} attribute.
+	 *
+	 * @param type The {@link SchemaInfo#setType(String) type} attribute.
+	 * @return The new element.
+	 */
+	public static final SchemaInfo schemaInfo(String type) {
+		return schemaInfo().setType(type);
+	}
+
+	/**
+	 * Creates an empty {@link SecuritySchemeInfo} element.
+	 *
+	 * @return The new element.
+	 */
+	public static final SecuritySchemeInfo securitySchemeInfo() {
+		return new SecuritySchemeInfo();
+	}
+
+	/**
+	 * Creates an {@link SecuritySchemeInfo} element with the specified {@link SecuritySchemeInfo#setType(String) type} attribute.
+	 *
+	 * @param type The {@link SecuritySchemeInfo#setType(String) type} attribute.
+	 * @return The new element.
+	 */
+	public static final SecuritySchemeInfo securitySchemeInfo(String type) {
+		return securitySchemeInfo().setType(type);
+	}
+
+	/**
+	 * Creates an empty {@link Server} element.
+	 *
+	 * @return The new element.
+	 */
+	public static final Server server() {
+		return new Server();
+	}
+
+	/**
+	 * Creates an {@link Server} element with the specified {@link Server#setUrl(URI) url} attribute.
+	 *
+	 * @param url The {@link Server#setUrl(URI) url} attribute.
+	 * @return The new element.
+	 */
+	public static final Server server(URI url) {
+		return server().setUrl(url);
+	}
+
+	/**
+	 * Creates an empty {@link Tag} element.
+	 *
+	 * @return The new element.
+	 */
+	public static final Tag tag() {
+		return new Tag();
+	}
+
+	/**
+	 * Creates an {@link Tag} element with the specified {@link Tag#setName(String) name} attribute.
+	 *
+	 * @param name The {@link Tag#setName(String) name} attribute.
+	 * @return The new element.
+	 */
+	public static final Tag tag(String name) {
+		return tag().setName(name);
+	}
+
+	/**
+	 * Creates an empty {@link Xml} element.
+	 *
+	 * @return The new element.
+	 */
+	public static final Xml xml() {
+		return new Xml();
+	}
+}
diff --git a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Operation.java b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Operation.java
new file mode 100644
index 000000000..35db3279c
--- /dev/null
+++ b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Operation.java
@@ -0,0 +1,309 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you 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.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.bean.openapi3;
+
+import static org.apache.juneau.internal.CollectionUtils.*;
+
+import java.util.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Describes a single API operation on a path.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='link'><a class="doclink" href="../../../../../index.html#jrs.OpenApi">Overview &gt; juneau-rest-server &gt; OpenAPI</a>
+ * </ul>
+ */
+@Bean(properties="tags,summary,description,externalDocs,operationId,parameters,requestBody,responses,callbacks,deprecated,security,servers,*")
+@FluentSetters
+public class Operation extends OpenApiElement {
+
+	private List<String> tags;
+	private String summary, description, operationId;
+	private ExternalDocumentation externalDocs;
+	private List<Parameter> parameters;
+	private RequestBodyInfo requestBody;
+	private Map<String,Response> responses;
+	private Map<String,Callback> callbacks;
+	private Boolean deprecated;
+	private List<SecurityRequirement> security;
+	private List<Server> servers;
+
+	/**
+	 * Default constructor.
+	 */
+	public Operation() {}
+
+	/**
+	 * Copy constructor.
+	 *
+	 * @param copyFrom The object to copy.
+	 */
+	public Operation(Operation copyFrom) {
+		super(copyFrom);
+		this.tags = copyOf(copyFrom.tags);
+		this.summary = copyFrom.summary;
+		this.description = copyFrom.description;
+		this.operationId = copyFrom.operationId;
+		this.externalDocs = copyFrom.externalDocs;
+		this.parameters = copyOf(copyFrom.parameters);
+		this.requestBody = copyFrom.requestBody;
+		this.responses = copyOf(copyFrom.responses);
+		this.callbacks = copyOf(copyFrom.callbacks);
+		this.deprecated = copyFrom.deprecated;
+		this.security = copyOf(copyFrom.security);
+		this.servers = copyOf(copyFrom.servers);
+	}
+
+	/**
+	 * Returns the tags list.
+	 *
+	 * @return The tags list.
+	 */
+	public List<String> getTags() {
+		return tags;
+	}
+
+	/**
+	 * Sets the tags list.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Operation setTags(List<String> value) {
+		this.tags = value;
+		return this;
+	}
+
+	/**
+	 * Returns the summary.
+	 *
+	 * @return The summary.
+	 */
+	public String getSummary() {
+		return summary;
+	}
+
+	/**
+	 * Sets the summary.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Operation setSummary(String value) {
+		this.summary = value;
+		return this;
+	}
+
+	/**
+	 * Returns the description.
+	 *
+	 * @return The description.
+	 */
+	public String getDescription() {
+		return description;
+	}
+
+	/**
+	 * Sets the description.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Operation setDescription(String value) {
+		this.description = value;
+		return this;
+	}
+
+	/**
+	 * Returns the operation ID.
+	 *
+	 * @return The operation ID.
+	 */
+	public String getOperationId() {
+		return operationId;
+	}
+
+	/**
+	 * Sets the operation ID.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Operation setOperationId(String value) {
+		this.operationId = value;
+		return this;
+	}
+
+	/**
+	 * Returns the external documentation.
+	 *
+	 * @return The external documentation.
+	 */
+	public ExternalDocumentation getExternalDocs() {
+		return externalDocs;
+	}
+
+	/**
+	 * Sets the external documentation.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Operation setExternalDocs(ExternalDocumentation value) {
+		this.externalDocs = value;
+		return this;
+	}
+
+	/**
+	 * Returns the parameters list.
+	 *
+	 * @return The parameters list.
+	 */
+	public List<Parameter> getParameters() {
+		return parameters;
+	}
+
+	/**
+	 * Sets the parameters list.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Operation setParameters(List<Parameter> value) {
+		this.parameters = value;
+		return this;
+	}
+
+	/**
+	 * Returns the request body.
+	 *
+	 * @return The request body.
+	 */
+	public RequestBodyInfo getRequestBody() {
+		return requestBody;
+	}
+
+	/**
+	 * Sets the request body.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Operation setRequestBody(RequestBodyInfo value) {
+		this.requestBody = value;
+		return this;
+	}
+
+	/**
+	 * Returns the responses map.
+	 *
+	 * @return The responses map.
+	 */
+	public Map<String,Response> getResponses() {
+		return responses;
+	}
+
+	/**
+	 * Sets the responses map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Operation setResponses(Map<String,Response> value) {
+		this.responses = value;
+		return this;
+	}
+
+	/**
+	 * Returns the callbacks map.
+	 *
+	 * @return The callbacks map.
+	 */
+	public Map<String,Callback> getCallbacks() {
+		return callbacks;
+	}
+
+	/**
+	 * Sets the callbacks map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Operation setCallbacks(Map<String,Callback> value) {
+		this.callbacks = value;
+		return this;
+	}
+
+	/**
+	 * Returns the deprecated flag.
+	 *
+	 * @return The deprecated flag.
+	 */
+	public Boolean getDeprecated() {
+		return deprecated;
+	}
+
+	/**
+	 * Sets the deprecated flag.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Operation setDeprecated(Boolean value) {
+		this.deprecated = value;
+		return this;
+	}
+
+	/**
+	 * Returns the security requirements list.
+	 *
+	 * @return The security requirements list.
+	 */
+	public List<SecurityRequirement> getSecurity() {
+		return security;
+	}
+
+	/**
+	 * Sets the security requirements list.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Operation setSecurity(List<SecurityRequirement> value) {
+		this.security = value;
+		return this;
+	}
+
+	/**
+	 * Returns the servers list.
+	 *
+	 * @return The servers list.
+	 */
+	public List<Server> getServers() {
+		return servers;
+	}
+
+	/**
+	 * Sets the servers list.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Operation setServers(List<Server> value) {
+		this.servers = value;
+		return this;
+	}
+}
diff --git a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Parameter.java b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Parameter.java
new file mode 100644
index 000000000..f1316c7a8
--- /dev/null
+++ b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/Parameter.java
@@ -0,0 +1,320 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you 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.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.bean.openapi3;
+
+import static org.apache.juneau.common.internal.Utils.*;
+import static org.apache.juneau.internal.CollectionUtils.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.marshaller.*;
+
+/**
+ * Describes a single operation parameter.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='link'><a class="doclink" href="../../../../../index.html#jrs.OpenApi">Overview &gt; juneau-rest-server &gt; OpenAPI</a>
+ * </ul>
+ */
+@Bean(properties="name,in,description,required,deprecated,allowEmptyValue,style,explode,allowReserved,schema,example,examples,*")
+@FluentSetters
+public class Parameter extends OpenApiElement {
+
+	private static final String[] VALID_IN = {"query", "header", "path", "cookie"};
+	private static final String[] VALID_STYLES = {"matrix", "label", "form", "simple", "spaceDelimited", "pipeDelimited", "deepObject"};
+
+	private String name, in, description, style;
+	private Boolean required, deprecated, allowEmptyValue, explode, allowReserved;
+	private SchemaInfo schema;
+	private Object example;
+	private Map<String,Example> examples;
+
+	/**
+	 * Default constructor.
+	 */
+	public Parameter() {}
+
+	/**
+	 * Copy constructor.
+	 *
+	 * @param copyFrom The object to copy.
+	 */
+	public Parameter(Parameter copyFrom) {
+		super(copyFrom);
+		this.name = copyFrom.name;
+		this.in = copyFrom.in;
+		this.description = copyFrom.description;
+		this.style = copyFrom.style;
+		this.required = copyFrom.required;
+		this.deprecated = copyFrom.deprecated;
+		this.allowEmptyValue = copyFrom.allowEmptyValue;
+		this.explode = copyFrom.explode;
+		this.allowReserved = copyFrom.allowReserved;
+		this.schema = copyFrom.schema;
+		this.example = copyFrom.example;
+		this.examples = copyOf(copyFrom.examples);
+	}
+
+	/**
+	 * Returns the parameter name.
+	 *
+	 * @return The parameter name.
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * Sets the parameter name.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Parameter setName(String value) {
+		this.name = value;
+		return this;
+	}
+
+	/**
+	 * Returns the parameter location.
+	 *
+	 * @return The parameter location.
+	 */
+	public String getIn() {
+		return in;
+	}
+
+	/**
+	 * Sets the parameter location.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Parameter setIn(String value) {
+		if (isStrict() && ! contains(value, VALID_IN))
+			throw new BasicRuntimeException(
+				"Invalid value passed in to setIn(String).  Value=''{0}'', valid values={1}",
+				value, Json5.of(VALID_IN)
+			);
+		this.in = value;
+		return this;
+	}
+
+	/**
+	 * Returns the description.
+	 *
+	 * @return The description.
+	 */
+	public String getDescription() {
+		return description;
+	}
+
+	/**
+	 * Sets the description.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Parameter setDescription(String value) {
+		this.description = value;
+		return this;
+	}
+
+	/**
+	 * Returns the style.
+	 *
+	 * @return The style.
+	 */
+	public String getStyle() {
+		return style;
+	}
+
+	/**
+	 * Sets the style.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Parameter setStyle(String value) {
+		if (isStrict() && ! contains(value, VALID_STYLES))
+			throw new BasicRuntimeException(
+				"Invalid value passed in to setStyle(String).  Value=''{0}'', valid values={1}",
+				value, Json5.of(VALID_STYLES)
+			);
+		this.style = value;
+		return this;
+	}
+
+	/**
+	 * Returns the required flag.
+	 *
+	 * @return The required flag.
+	 */
+	public Boolean getRequired() {
+		return required;
+	}
+
+	/**
+	 * Sets the required flag.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Parameter setRequired(Boolean value) {
+		this.required = value;
+		return this;
+	}
+
+	/**
+	 * Returns the deprecated flag.
+	 *
+	 * @return The deprecated flag.
+	 */
+	public Boolean getDeprecated() {
+		return deprecated;
+	}
+
+	/**
+	 * Sets the deprecated flag.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Parameter setDeprecated(Boolean value) {
+		this.deprecated = value;
+		return this;
+	}
+
+	/**
+	 * Returns the allow empty value flag.
+	 *
+	 * @return The allow empty value flag.
+	 */
+	public Boolean getAllowEmptyValue() {
+		return allowEmptyValue;
+	}
+
+	/**
+	 * Sets the allow empty value flag.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Parameter setAllowEmptyValue(Boolean value) {
+		this.allowEmptyValue = value;
+		return this;
+	}
+
+	/**
+	 * Returns the explode flag.
+	 *
+	 * @return The explode flag.
+	 */
+	public Boolean getExplode() {
+		return explode;
+	}
+
+	/**
+	 * Sets the explode flag.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Parameter setExplode(Boolean value) {
+		this.explode = value;
+		return this;
+	}
+
+	/**
+	 * Returns the allow reserved flag.
+	 *
+	 * @return The allow reserved flag.
+	 */
+	public Boolean getAllowReserved() {
+		return allowReserved;
+	}
+
+	/**
+	 * Sets the allow reserved flag.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Parameter setAllowReserved(Boolean value) {
+		this.allowReserved = value;
+		return this;
+	}
+
+	/**
+	 * Returns the schema.
+	 *
+	 * @return The schema.
+	 */
+	public SchemaInfo getSchema() {
+		return schema;
+	}
+
+	/**
+	 * Sets the schema.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Parameter setSchema(SchemaInfo value) {
+		this.schema = value;
+		return this;
+	}
+
+	/**
+	 * Returns the example.
+	 *
+	 * @return The example.
+	 */
+	public Object getExample() {
+		return example;
+	}
+
+	/**
+	 * Sets the example.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Parameter setExample(Object value) {
+		this.example = value;
+		return this;
+	}
+
+	/**
+	 * Returns the examples map.
+	 *
+	 * @return The examples map.
+	 */
+	public Map<String,Example> getExamples() {
+		return examples;
+	}
+
+	/**
+	 * Sets the examples map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public Parameter setExamples(Map<String,Example> value) {
+		this.examples = value;
+		return this;
+	}
+}
diff --git a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/PathItem.java b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/PathItem.java
new file mode 100644
index 000000000..15dcd6fe2
--- /dev/null
+++ b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/PathItem.java
@@ -0,0 +1,303 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you 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.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.bean.openapi3;
+
+import static org.apache.juneau.internal.CollectionUtils.*;
+
+import java.util.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Describes the operations available on a single path.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='link'><a class="doclink" href="../../../../../index.html#jrs.OpenApi">Overview &gt; juneau-rest-server &gt; OpenAPI</a>
+ * </ul>
+ */
+@Bean(properties="summary,description,get,put,post,delete,options,head,patch,trace,servers,parameters,*")
+@FluentSetters
+public class PathItem extends OpenApiElement {
+
+	private String summary, description;
+	private Operation get, put, post, delete, options, head, patch, trace;
+	private List<Server> servers;
+	private List<Parameter> parameters;
+
+	/**
+	 * Default constructor.
+	 */
+	public PathItem() {}
+
+	/**
+	 * Copy constructor.
+	 *
+	 * @param copyFrom The object to copy.
+	 */
+	public PathItem(PathItem copyFrom) {
+		super(copyFrom);
+		this.summary = copyFrom.summary;
+		this.description = copyFrom.description;
+		this.get = copyFrom.get;
+		this.put = copyFrom.put;
+		this.post = copyFrom.post;
+		this.delete = copyFrom.delete;
+		this.options = copyFrom.options;
+		this.head = copyFrom.head;
+		this.patch = copyFrom.patch;
+		this.trace = copyFrom.trace;
+		this.servers = copyOf(copyFrom.servers);
+		this.parameters = copyOf(copyFrom.parameters);
+	}
+
+	/**
+	 * Returns the summary.
+	 *
+	 * @return The summary.
+	 */
+	public String getSummary() {
+		return summary;
+	}
+
+	/**
+	 * Sets the summary.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public PathItem setSummary(String value) {
+		this.summary = value;
+		return this;
+	}
+
+	/**
+	 * Returns the description.
+	 *
+	 * @return The description.
+	 */
+	public String getDescription() {
+		return description;
+	}
+
+	/**
+	 * Sets the description.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public PathItem setDescription(String value) {
+		this.description = value;
+		return this;
+	}
+
+	/**
+	 * Returns the GET operation.
+	 *
+	 * @return The GET operation.
+	 */
+	public Operation getGet() {
+		return get;
+	}
+
+	/**
+	 * Sets the GET operation.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public PathItem setGet(Operation value) {
+		this.get = value;
+		return this;
+	}
+
+	/**
+	 * Returns the PUT operation.
+	 *
+	 * @return The PUT operation.
+	 */
+	public Operation getPut() {
+		return put;
+	}
+
+	/**
+	 * Sets the PUT operation.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public PathItem setPut(Operation value) {
+		this.put = value;
+		return this;
+	}
+
+	/**
+	 * Returns the POST operation.
+	 *
+	 * @return The POST operation.
+	 */
+	public Operation getPost() {
+		return post;
+	}
+
+	/**
+	 * Sets the POST operation.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public PathItem setPost(Operation value) {
+		this.post = value;
+		return this;
+	}
+
+	/**
+	 * Returns the DELETE operation.
+	 *
+	 * @return The DELETE operation.
+	 */
+	public Operation getDelete() {
+		return delete;
+	}
+
+	/**
+	 * Sets the DELETE operation.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public PathItem setDelete(Operation value) {
+		this.delete = value;
+		return this;
+	}
+
+	/**
+	 * Returns the OPTIONS operation.
+	 *
+	 * @return The OPTIONS operation.
+	 */
+	public Operation getOptions() {
+		return options;
+	}
+
+	/**
+	 * Sets the OPTIONS operation.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public PathItem setOptions(Operation value) {
+		this.options = value;
+		return this;
+	}
+
+	/**
+	 * Returns the HEAD operation.
+	 *
+	 * @return The HEAD operation.
+	 */
+	public Operation getHead() {
+		return head;
+	}
+
+	/**
+	 * Sets the HEAD operation.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public PathItem setHead(Operation value) {
+		this.head = value;
+		return this;
+	}
+
+	/**
+	 * Returns the PATCH operation.
+	 *
+	 * @return The PATCH operation.
+	 */
+	public Operation getPatch() {
+		return patch;
+	}
+
+	/**
+	 * Sets the PATCH operation.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public PathItem setPatch(Operation value) {
+		this.patch = value;
+		return this;
+	}
+
+	/**
+	 * Returns the TRACE operation.
+	 *
+	 * @return The TRACE operation.
+	 */
+	public Operation getTrace() {
+		return trace;
+	}
+
+	/**
+	 * Sets the TRACE operation.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public PathItem setTrace(Operation value) {
+		this.trace = value;
+		return this;
+	}
+
+	/**
+	 * Returns the servers list.
+	 *
+	 * @return The servers list.
+	 */
+	public List<Server> getServers() {
+		return servers;
+	}
+
+	/**
+	 * Sets the servers list.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public PathItem setServers(List<Server> value) {
+		this.servers = value;
+		return this;
+	}
+
+	/**
+	 * Returns the parameters list.
+	 *
+	 * @return The parameters list.
+	 */
+	public List<Parameter> getParameters() {
+		return parameters;
+	}
+
+	/**
+	 * Sets the parameters list.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public PathItem setParameters(List<Parameter> value) {
+		this.parameters = value;
+		return this;
+	}
+}
diff --git a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/SchemaInfo.java b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/SchemaInfo.java
index e1c51ca84..3f68e1e22 100644
--- a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/SchemaInfo.java
+++ b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/SchemaInfo.java
@@ -18,7 +18,6 @@ import static org.apache.juneau.internal.ConverterUtils.*;
 import java.util.*;
 
 import org.apache.juneau.annotation.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.internal.*;
 
@@ -1280,7 +1279,7 @@ public class SchemaInfo extends OpenApiElement {
 	/**
 	 * Resolves any <js>"$ref"</js> attributes in this element.
 	 *
-	 * @param swagger The swagger document containing the definitions.
+	 * @param openApi The swagger document containing the definitions.
 	 * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops.
 	 * @param maxDepth
 	 * 	The maximum depth to resolve references.
@@ -1290,26 +1289,27 @@ public class SchemaInfo extends OpenApiElement {
 	 * 	This object with references resolved.
 	 * 	<br>May or may not be the same object.
 	 */
-	public SchemaInfo resolveRefs(Swagger swagger, Deque<String> refStack, int maxDepth) {
+	public SchemaInfo resolveRefs(OpenApi openApi, Deque<String> refStack, int maxDepth) {
 
 		if (ref != null) {
 			if (refStack.contains(ref) || refStack.size() >= maxDepth)
 				return this;
 			refStack.addLast(ref);
-			var r = swagger.findRef(ref, SchemaInfo.class).resolveRefs(swagger, refStack, maxDepth);
+			var r = openApi.findRef(ref, SchemaInfo.class);
+			r = r.resolveRefs(openApi, refStack, maxDepth);
 			refStack.removeLast();
 			return r;
 		}
 
 		if (items != null)
-			items = items.resolveRefs(swagger, refStack, maxDepth);
+			items = items.resolveRefs(openApi, refStack, maxDepth);
 
 		if (properties != null)
 			for (var e : properties.entrySet())
-				e.setValue(e.getValue().resolveRefs(swagger, refStack, maxDepth));
+				e.setValue(e.getValue().resolveRefs(openApi, refStack, maxDepth));
 
 		if (additionalProperties != null)
-			additionalProperties = additionalProperties.resolveRefs(swagger, refStack, maxDepth);
+			additionalProperties = additionalProperties.resolveRefs(openApi, refStack, maxDepth);
 
 		this.example = null;
 
diff --git a/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/SecurityRequirement.java b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/SecurityRequirement.java
new file mode 100644
index 000000000..8dac859f5
--- /dev/null
+++ b/juneau-bean/juneau-bean-openapi3/src/main/java/org/apache/juneau/bean/openapi3/SecurityRequirement.java
@@ -0,0 +1,83 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you 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.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.bean.openapi3;
+
+import static org.apache.juneau.internal.CollectionUtils.*;
+
+import java.util.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Lists the required security schemes for this operation.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='link'><a class="doclink" href="../../../../../index.html#jrs.OpenApi">Overview &gt; juneau-rest-server &gt; OpenAPI</a>
+ * </ul>
+ */
+@Bean(properties="*")
+@FluentSetters
+public class SecurityRequirement extends OpenApiElement {
+
+	private Map<String,List<String>> requirements;
+
+	/**
+	 * Default constructor.
+	 */
+	public SecurityRequirement() {}
+
+	/**
+	 * Copy constructor.
+	 *
+	 * @param copyFrom The object to copy.
+	 */
+	public SecurityRequirement(SecurityRequirement copyFrom) {
+		super(copyFrom);
+		this.requirements = copyOf(copyFrom.requirements);
+	}
+
+	/**
+	 * Returns the security requirements map.
+	 *
+	 * @return The security requirements map.
+	 */
+	public Map<String,List<String>> getRequirements() {
+		return requirements;
+	}
+
+	/**
+	 * Sets the security requirements map.
+	 *
+	 * @param value The new value for this property.
+	 * @return This object.
+	 */
+	public SecurityRequirement setRequirements(Map<String,List<String>> value) {
+		this.requirements = value;
+		return this;
+	}
+
+	/**
+	 * Adds a security requirement.
+	 *
+	 * @param schemeName The security scheme name.
+	 * @param scopes The required scopes.
+	 * @return This object.
+	 */
+	public SecurityRequirement addRequirement(String schemeName, String... scopes) {
+		if (requirements == null)
+			requirements = new LinkedHashMap<>();
+		requirements.put(schemeName, Arrays.asList(scopes));
+		return this;
+	}
+}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/DTOs_RoundTripTest.java b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/DTOs_RoundTripTest.java
index 416628883..5cbf6c881 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/DTOs_RoundTripTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/DTOs_RoundTripTest.java
@@ -16,7 +16,6 @@ import static org.apache.juneau.TestUtils.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 import org.apache.juneau.bean.jsonschema.*;
-import org.apache.juneau.dto.jsonschema.*;
 import org.junit.jupiter.params.*;
 import org.junit.jupiter.params.provider.*;
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/atom/AtomTest.java b/juneau-utest/src/test/java/org/apache/juneau/bean/atom/AtomTest.java
similarity index 99%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/atom/AtomTest.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/atom/AtomTest.java
index f150fdaf2..65ec43495 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/atom/AtomTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/atom/AtomTest.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.atom;
+package org.apache.juneau.bean.atom;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.bean.atom.AtomBuilder.*;
@@ -19,7 +19,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.net.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.atom.*;
 import org.apache.juneau.xml.*;
 import org.junit.jupiter.api.*;
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/html5/Html5BasicTest.java b/juneau-utest/src/test/java/org/apache/juneau/bean/html5/Html5BasicTest.java
similarity index 96%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/html5/Html5BasicTest.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/html5/Html5BasicTest.java
index 0d2ac4f34..044c521a9 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/html5/Html5BasicTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/html5/Html5BasicTest.java
@@ -10,13 +10,12 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.html5;
+package org.apache.juneau.bean.html5;
 
 import static org.apache.juneau.bean.html5.HtmlBuilder.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.html5.*;
 import org.junit.jupiter.api.*;
 
 class Html5BasicTest extends SimpleTestBase {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/html5/Html5Template_ComboRoundTripTest.java b/juneau-utest/src/test/java/org/apache/juneau/bean/html5/Html5Template_ComboRoundTripTest.java
similarity index 97%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/html5/Html5Template_ComboRoundTripTest.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/html5/Html5Template_ComboRoundTripTest.java
index eb25df6f6..c1c8689c2 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/html5/Html5Template_ComboRoundTripTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/html5/Html5Template_ComboRoundTripTest.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.html5;
+package org.apache.juneau.bean.html5;
 
 import static org.apache.juneau.assertions.Verify.*;
 import static org.apache.juneau.bean.html5.HtmlBuilder.*;
@@ -19,7 +19,6 @@ import java.lang.reflect.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
-import org.apache.juneau.bean.html5.*;
 
 /**
  * Tests serialization of HTML5 templates.
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/html5/Html5_ComboRoundTripTest.java b/juneau-utest/src/test/java/org/apache/juneau/bean/html5/Html5_ComboRoundTripTest.java
similarity index 99%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/html5/Html5_ComboRoundTripTest.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/html5/Html5_ComboRoundTripTest.java
index e6e6dabc3..db07b9193 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/html5/Html5_ComboRoundTripTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/html5/Html5_ComboRoundTripTest.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.html5;
+package org.apache.juneau.bean.html5;
 
 import static org.apache.juneau.assertions.Verify.*;
 import static org.apache.juneau.bean.html5.HtmlBuilder.*;
@@ -22,8 +22,6 @@ import java.lang.reflect.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.html5.*;
-import org.apache.juneau.bean.html5.Map;
 import org.apache.juneau.internal.*;
 
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/jsonschema/JsonSchema_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchema_Test.java
similarity index 98%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/jsonschema/JsonSchema_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchema_Test.java
index 31e3c2a7b..53268125b 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/jsonschema/JsonSchema_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchema_Test.java
@@ -10,14 +10,13 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.jsonschema;
+package org.apache.juneau.bean.jsonschema;
 
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.net.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.jsonschema.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Contact_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Contact_Test.java
similarity index 97%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Contact_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Contact_Test.java
index 03daac638..534e79043 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Contact_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Contact_Test.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -18,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.net.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/ExternalDocumentation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/ExternalDocumentation_Test.java
similarity index 97%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/ExternalDocumentation_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/ExternalDocumentation_Test.java
index 56aadd6ff..3b11ced5d 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/ExternalDocumentation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/ExternalDocumentation_Test.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -18,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.net.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/HeaderInfo_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/HeaderInfo_Test.java
similarity index 99%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/HeaderInfo_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/HeaderInfo_Test.java
index 78ec56b13..a159b4d37 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/HeaderInfo_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/HeaderInfo_Test.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.bean.swagger.SwaggerBuilder.*;
@@ -19,7 +19,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Info_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Info_Test.java
similarity index 98%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Info_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Info_Test.java
index 8f55a589a..2e692c14d 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Info_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Info_Test.java
@@ -10,14 +10,13 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.bean.swagger.SwaggerBuilder.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Items_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Items_Test.java
similarity index 99%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Items_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Items_Test.java
index 31a0513a4..c45067b36 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Items_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Items_Test.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.bean.swagger.SwaggerBuilder.*;
@@ -19,7 +19,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/License_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/License_Test.java
similarity index 97%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/License_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/License_Test.java
index 2baf0d94e..4188870cf 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/License_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/License_Test.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -18,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.net.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Operation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Operation_Test.java
similarity index 99%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Operation_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Operation_Test.java
index 374fe7750..cf5f922e7 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Operation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Operation_Test.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.bean.swagger.SwaggerBuilder.*;
@@ -19,7 +19,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/ParameterInfo_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/ParameterInfo_Test.java
similarity index 99%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/ParameterInfo_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/ParameterInfo_Test.java
index 8d42167f5..db678c517 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/ParameterInfo_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/ParameterInfo_Test.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.bean.swagger.SwaggerBuilder.*;
@@ -19,7 +19,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/ResponseInfo_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/ResponseInfo_Test.java
similarity index 98%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/ResponseInfo_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/ResponseInfo_Test.java
index a131267c4..d61113bcb 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/ResponseInfo_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/ResponseInfo_Test.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.bean.swagger.SwaggerBuilder.*;
@@ -19,7 +19,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/SchemaInfo_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/SchemaInfo_Test.java
similarity index 99%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/SchemaInfo_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/SchemaInfo_Test.java
index 186c13aec..331f839e4 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/SchemaInfo_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/SchemaInfo_Test.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.bean.swagger.SwaggerBuilder.*;
@@ -19,7 +19,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/SecurityScheme_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/SecurityScheme_Test.java
similarity index 98%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/SecurityScheme_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/SecurityScheme_Test.java
index bf4f45187..a8414f42d 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/SecurityScheme_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/SecurityScheme_Test.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.junit.jupiter.api.Assertions.*;
@@ -18,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/SwaggerBuilder_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/SwaggerBuilder_Test.java
similarity index 98%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/SwaggerBuilder_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/SwaggerBuilder_Test.java
index 8d01fdefe..0e7041cb4 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/SwaggerBuilder_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/SwaggerBuilder_Test.java
@@ -10,13 +10,12 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.bean.swagger.SwaggerBuilder.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
 import org.junit.jupiter.api.*;
 
 /**
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Swagger_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Swagger_Test.java
similarity index 99%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Swagger_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Swagger_Test.java
index 2aa1f77e6..568fb07bd 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Swagger_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Swagger_Test.java
@@ -10,7 +10,7 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.bean.swagger.SwaggerBuilder.*;
@@ -19,8 +19,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
-import org.apache.juneau.bean.swagger.Tag;
 import org.apache.juneau.collections.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Tag_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Tag_Test.java
similarity index 98%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Tag_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Tag_Test.java
index ed41be930..32ca4883d 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Tag_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Tag_Test.java
@@ -10,14 +10,13 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.bean.swagger.SwaggerBuilder.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.Tag;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Xml_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Xml_Test.java
similarity index 98%
rename from juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Xml_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Xml_Test.java
index 01bfd233c..cda73a98a 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/dto/swagger/Xml_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/swagger/Xml_Test.java
@@ -10,13 +10,12 @@
 // * "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.                                              *
 // ***************************************************************************************************************************
-package org.apache.juneau.dto.swagger;
+package org.apache.juneau.bean.swagger;
 
 import static org.apache.juneau.TestUtils.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.bean.swagger.*;
 import org.apache.juneau.common.internal.*;
 import org.apache.juneau.json.*;
 import org.junit.jupiter.api.*;