Merge pull request #109 from Attacktive/2.3-gae

docs: correct typos
diff --git a/README.md b/README.md
index ea2fb9d..b824243 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-Apache FreeMarker {version}
-===========================
+Apache FreeMarker™ {version}
+============================
 
 [![Build status](https://github.com/apache/freemarker/actions/workflows/ci.yml/badge.svg)](https://github.com/apache/freemarker/actions/workflows/ci.yml)
 
@@ -19,11 +19,11 @@
 to actually signing a Contributor License Agreement.
 
 
-What is Apache FreeMarker?
---------------------------
+What is Apache FreeMarker™?
+---------------------------
 
-FreeMarker is a "template engine"; a generic tool to generate text
-output (anything from HTML to auto generated source code) based on
+Apache FreeMarker™ is a "template engine"; a generic tool to generate
+text output (anything from HTML to auto generated source code) based on
 templates. It's a Java package, a class library for Java programmers.
 It's not an application for end-users in itself, but something that
 programmers can embed into their products. FreeMarker is designed to
@@ -37,7 +37,7 @@
 
 FreeMarker is licensed under the Apache License, Version 2.0.
 
-See the LICENSE file for more details!
+See the `LICENSE` file for more details!
 
 
 Documentation
@@ -68,9 +68,9 @@
 ```
 
 Otherwise, simply copy `freemarker.jar` to a location where your Java
-application's ClassLoader will find it. For example, if you are using
+application's `ClassLoader` will find it. For example, if you are using
 FreeMarker in a web application, you probably want to put
-freemarker.jar into the WEB-INF/lib directory of your web application.
+`freemarker.jar` into the `WEB-INF/lib` directory of your web application.
 
 FreeMarker has no required dependencies. It has several optional
 dependencies, but usually you don't have to deal with them, because if
@@ -79,13 +79,13 @@
 
 Attention: If you upgrade to OpenJDK 9 or later, and you are using
 XPath queries in templates, you will need to add Apache Xalan as a
-dependency, as freemarker.ext.dom can't use the XPath support
+dependency, as `freemarker.ext.dom` can't use the XPath support
 included in OpenJDK anymore. It's not needed on Oracle Java 9,
 or if FreeMarker is configured to use Jaxen for XPath.
 
 The minimum required Java version is currently Java SE 8. (The presence
-of a later version may be detected on runtime and utilized by
-FreeMarker.)
+of a later version is detected on runtime and utilized by FreeMarker
+automatically.)
 
 
 Change log
@@ -95,7 +95,7 @@
 https://freemarker.apache.org/docs/app_versions.html
 
 Offline:
-In the binary release, open documentation/index.html, and you will find the
+In the binary release, open `documentation/index.html`, and you will find the
 link.
 
 
@@ -106,8 +106,10 @@
 the source code repository. See repository locations here:
 https://freemarker.apache.org/sourcecode.html
 
-You need JDK 8, JDK 16 and JDK 17 (only for some tests) to be installed
+You need JDK 8, JDK 16, and JDK 17 (only for some tests) to be installed
 (and [visible to Gradle](https://docs.gradle.org/current/userguide/toolchains.html)).
+That's because different parts of the source code target different Java versions,
+and Gradle requires the exact JDK version (not higher) for each.
 
 Be sure that your default Java version (which Gradle should use automatically) is at
 least 16!
@@ -118,40 +120,67 @@
 [from GitHub source code page](https://github.com/apache/freemarker/tree/2.3-gae/gradle/wrapper)!
 (Or, use your own Gradle installation instead of `gradlew`.)
 
-To build `freemarker.jar`, just issue `./gradlew jar` in the project root directory,
-and it should download all dependencies automatically and build `freemarker.jar`.
+To build `freemarker.jar`, just issue `./gradlew jar` (`gradlew.bat jar` on Windows) in the
+project root directory, and it should download all dependencies automatically, and build
+`freemarker.jar`.
 
 To run all JUnit tests and some other checks, issue `./gradlew check`. (Avoid the
 `test` task, as that will only run the tests of the `core` source set.)
 
 To generate documentation, issue `./gradlew javadoc` and `./gradlew manualOffline`.
 
+To build the distribution artifacts (the `tgz`-s that people can download), run `./gradlew build`. However,
+for a stable (non-`SNAPSHOT`) version number, you must set up signing, or disable that verification
+with `freemarker.allowUnsignedReleaseBuild=true`; see `gradle.properties` in this project for those!
+
+Reproducible builds: If the resulting `freemarker.jar` is not identical with the official jar, see the build environment
+in the `.buildinfo` file packed into the official source distribution, and also into the Maven "sources" artifact! At
+least with identical Java versions, the resulting `freemarker.jar` meant to match exactly.
+
+
+### Maven-related build tasks
+
 To see how the project would be deployed to Maven Central, issue
 `./gradlew publishAllPublicationsToLocalRepository`,
 and check the `build/local-deployment` directory.
+ 
+To publish to the Apache Maven Repository (from where you can also promote releases to the Maven Central Repository)
+issue `.\gradlew publish`. Note that for this the following Gradle properties must be properly set
+(in `gradle.properties`, or pass them via `-P<name>=<value>` arguments):
+`freemarker.signMethod`, `freemarker.deploy.apache.user`, `freemarker.deploy.apache.password`.
 
-To build the distribution artifacts (the `tgz`-s that people can download), run `./gradlew build`.However,
-
-For a stable (non-SNAPSHOT) version number, you will need to set up signing, or disable that verification;
-see `gradle.properties` in this project for those!  
-
-See `gradle.properties` for some Gradle properties that you may what to set,
-especially if you are building a release.
-
-Reproducible builds: If the resulting `freemarker.jar` is not identical with the official jar, see the build environment
-in the `.buildinfo` file packed into the official source distribution, and also into the Maven "sources" artifact!  
 
 
 IDE setup
 ---------
 
+### IntelliJ IDEA
+
+Originally done on IntelliJ IDEA Community 2023.3.2:
+
+- "File" -> "Open": Select the "settings.gradle.kts" within the freemarker root directory.
+- If the project fails to load (or build), then adjust the following configuration
+  in "File" -> "Settings" -> "Build, Execution, Deployment" -> "Build Tools" -> "Gradle":
+  - Gradle JVM: JDK 16 (or higher)
+  - Build and run using: "Gradle"
+  - Run tests using: "Gradle"
+
+- "File" -> "Settings"
+  - Under "Editor" / "Code style", import and use
+    freemarker/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
+  - Under "Editor" / "Inspections", import and use
+    freemarker/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml
+  - Copy the copyright header comment from some of the java files, then
+    under "Editor" / "Copyright" / "Copyright Profiles" click "+", enter "ASL2" as name,
+    then paste the copyright header. Delete the `/*` and ` */` lines, and the ` *`
+    prefixes (to select columns of text, hold Alt while selecting with the mouse.) Then
+    go back to "Copyright" in the tree, and set "Default project copyright" to "ASL2".
+
 ### Eclipse
 
-Below you find the step-by-step setup for Eclipse (originally done on Mars.1):
+This section wasn't updated long ago. But you should import the project as any other
+Gradle project. After that, it's recommended to set these preferences (based on Eclipse Mars):
 
-- Start Eclipse
-- You may prefer to start a new workspace (File -> "Switch workspace"), but
-  it's optional.
 - Window -> Preferences
   - General -> Workspace, set the text file encoding
     to "UTF-8". (Or, you can set the same later on project level instead.)
@@ -186,15 +215,6 @@
 - Project -> Properties -> Java Compiler
   - In Errors/Warnings, check in "Enable project specific settings", then set
     "Forbidden reference (access rules)" from "Error" to "Warning".
-- You will still have errors on these java files (because different java
-  files depend on different versions of the same library, and Eclipse can't
-  handle that). Exclude those java files from the Build Path (in the Package
-  Explorer, right-click on the problematic file -> "Build Path" -> "Exclude"):
-    _Jython20*.java,
-    _Jython22*.java,
-    _FreeMarkerPageContext2.java,
-    FreeMarkerJspFactory2.java,
-  Also, close these files if they are open. Now you shouldn't have any errors.
 - At Project -> Properties -> Java Code Style -> Formatter, check in "Enable
   project specific settings", and then select "FreeMarker" as active profile.
 - At Project -> Properties -> Java Editor -> Save Actions, check "Enable project
@@ -203,7 +223,7 @@
   last should contain "Add missing @Override annotations",
   "Add missing @Override annotations to implementations of interface methods",
   "Add missing @Deprecated annotations", and "Remove unnecessary cast").
-- Right click on the project -> Run As -> JUnit Test
+- Right-click on the project -> Run As -> JUnit Test
   It should run without problems (all green).
 - It's highly recommended to use the Eclipse FindBugs plugin.
   - Install it from Eclipse Marketplace (3.0.1 as of this writing)
@@ -213,25 +233,3 @@
   - Project -> Properties -> FindBugs -> [x] Run Automatically
   - There should 0 errors. But sometimes the plugin fails to take the
     @SuppressFBWarnings annotations into account; then use Project -> Clean. 
-
-### IntelliJ IDEA
-
-Originally done on IntelliJ IDEA Community 2023.3.2:
-
-- "File" -> "Open": Select the "settings.gradle.kts" within the freemarker root directory.
-- If the project fails to load (or build), then adjust the following configuration
-  in "File" -> "Settings" -> "Build, Execution, Deployment" -> "Build Tools" -> "Gradle":
-  - Gradle JVM: JDK 16 (or higher)
-  - Build and run using: "Gradle"
-  - Run tests using: "Gradle"
-
-- "File" -> "Settings"
-  - Under "Editor" / "Code style", import and use
-    freemarker/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
-  - Under "Editor" / "Inspections", import and use
-    freemarker/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml
-  - Copy the copyright header comment from some of the java files, then
-    under "Editor" / "Copyright" / "Copyright Profiles" click "+", enter "ASL2" as name,
-    then paste the copyright header. Delete the `/*` and ` */` lines, and the ` *`
-    prefixes (to select columns of text, hold Alt while selecting with the mouse.) Then
-    go back to "Copyright" in the tree, and set "Default project copyright" to "ASL2".
diff --git a/build.gradle.kts b/build.gradle.kts
index 548df91..1cf06bb 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -522,6 +522,7 @@
                 "osgi.bnd",
                 "rat-excludes",
                 "gradlew.bat",
+                "gradle.properties",
                 "gradle/**"
         )
         exclude(
diff --git a/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt
index 35dd452..2656afa 100644
--- a/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt
+++ b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt
@@ -259,7 +259,7 @@
         .get()
 
     val allowUnsignedReleaseBuild = context.providers
-        .gradleProperty("allowUnsignedReleaseBuild")
+        .gradleProperty("freemarker.allowUnsignedReleaseBuild")
         .map { it.toBoolean() }
         .getOrElse(false)
 
diff --git a/freemarker-core/src/main/java/freemarker/core/DotBeforeMethodCall.java b/freemarker-core/src/main/java/freemarker/core/DotBeforeMethodCall.java
index 17ae026..7fd3e1b 100644
--- a/freemarker-core/src/main/java/freemarker/core/DotBeforeMethodCall.java
+++ b/freemarker-core/src/main/java/freemarker/core/DotBeforeMethodCall.java
@@ -30,9 +30,11 @@
  * Like {@link Dot}, but when used before method call (but as of 2.3.33, before 0-argument calls only), as in
  * {@code obj.key()}. The reason it's only used before 0-argument calls (as of 2.3.33 at least) is that it adds some
  * overhead, and this {@link Dot} subclass was added to implement
- * {@link ZeroArgumentNonVoidMethodPolicy#BOTH_PROPERTY_AND_METHOD}
+ * {@link ZeroArgumentNonVoidMethodPolicy#BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD}
  * (via {@link BeansWrapper.MethodAppearanceDecision#setMethodInsteadOfPropertyValueBeforeCall(boolean)}). We don't
  * necessarily want to go beyond that hack, as we don't have separate method namespace in the template language.
+ *
+ * @since 2.3.33
  */
 class DotBeforeMethodCall extends Dot {
     public DotBeforeMethodCall(Dot dot) {
diff --git a/freemarker-core/src/main/java/freemarker/core/TemplateProcessingTracer.java b/freemarker-core/src/main/java/freemarker/core/TemplateProcessingTracer.java
index d16be39..b40b380 100644
--- a/freemarker-core/src/main/java/freemarker/core/TemplateProcessingTracer.java
+++ b/freemarker-core/src/main/java/freemarker/core/TemplateProcessingTracer.java
@@ -27,7 +27,7 @@
  * <p>
  * Use {@link Environment#setTemplateProcessingTracer(TemplateProcessingTracer)} to set a tracer for the current
  * environment.
- * 
+ *
  * @since 2.3.33
  */
 public interface TemplateProcessingTracer {
@@ -36,8 +36,6 @@
      * Invoked by {@link Environment} whenever it starts processing a new template element. A template element is a
      * directive call, an interpolation (like <code>${...}</code>), a comment block, or static text. Expressions
      * are not template elements.
-     * 
-     * @since 2.3.23
      */
     void enterElement(Environment env, TracedElement tracedElement);
 
@@ -45,13 +43,14 @@
      * Invoked by {@link Environment} whenever it completes processing a new template element.
      *
      * @see #enterElement(Environment, TracedElement)
-     * 
-     * @since 2.3.23
      */
     void exitElement(Environment env, TracedElement tracedElement);
 
     /**
-     * Information about the template element that we enter of exit.
+     * Information about the template element that we enter of exit; do NOT implement this (outside FreeMarker), as
+     * new methods can be added to this anytime!
+     *
+     * @since 2.3.33
      */
     interface TracedElement {
         /**
diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapper.java b/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapper.java
index f7993f1..2a7f912 100644
--- a/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -107,13 +107,7 @@
     /**
      * At this level of exposure, all methods and properties of the wrapped
      * objects are exposed to the template except methods that are deemed
-     * not safe. The not safe methods are java.lang.Object methods wait() and
-     * notify(), java.lang.Class methods getClassLoader() and newInstance(),
-     * java.lang.reflect.Method and java.lang.reflect.Constructor invoke() and
-     * newInstance() methods, all java.lang.reflect.Field set methods, all 
-     * java.lang.Thread and java.lang.ThreadGroup methods that can change its 
-     * state, as well as the usual suspects in java.lang.System and
-     * java.lang.Runtime.
+     * not safe by the {@link MemberAccessPolicy}.
      *
      * <p>Note that the {@link MemberAccessPolicy} will further restrict what's visible. That mechanism was introduced
      * much later than "exposure levels", and it's the primary place to look at if you are concerned with safety.
@@ -121,19 +115,21 @@
     public static final int EXPOSE_SAFE = 1;
     
     /**
-     * At this level of exposure, only property getters are exposed.
-     * Additionally, property getters that map to unsafe methods are not
-     * exposed (i.e. Class.classLoader and Thread.contextClassLoader).
+     * At this level of exposure, only Java Bean properties are exposed. For example, if you have
+     * {@code public int getX()} in a public class, then you can access that in templates like {@code obj.x} (but
+     * not as {@code obj.getX()}).
      *
-     * <p>Note that the {@link MemberAccessPolicy} will further restrict what's visible.
+     * <p>Note that the {@link MemberAccessPolicy} will further restricts what's visible.
+     * Java Bean properties (like {@code obj.x} earlier) whose read method (like {@code getX()} earlier) is not
+     * accessible according the policy will not be visible.
      */
     public static final int EXPOSE_PROPERTIES_ONLY = 2;
 
     /**
-     * At this level of exposure, no bean properties and methods are exposed.
+     * At this level of exposure, no Java Bean properties, and no methods are exposed.
      * Only map items, resource bundle items, and objects retrieved through
      * the generic get method (on objects of classes that have a generic get
-     * method) can be retrieved through the hash interface. You might want to 
+     * method) can be retrieved through the {@link TemplateHashModel} interface. You might want to
      * call {@link #setMethodsShadowItems(boolean)} with {@code false} value to
      * speed up map item retrieval.
      */
@@ -270,7 +266,8 @@
      *     <li>
      *       <p>2.3.33 (or higher):
      *       The default of {@link BeansWrapper#setRecordZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)}
-     *       has changed to {@link ZeroArgumentNonVoidMethodPolicy#BOTH_PROPERTY_AND_METHOD}, from
+     *       has changed to
+     *       {@link ZeroArgumentNonVoidMethodPolicy#BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD}, from
      *       {@link ZeroArgumentNonVoidMethodPolicy#METHOD_ONLY}. This means that Java record public methods with
      *       0-arguments and non-void return type are now exposed both as properties, and as methods, while earlier they
      *       were only exposed as methods. That is, if in a record you have {@code public String name()}, now in
@@ -674,8 +671,9 @@
     /**
      * Sets the {@link ZeroArgumentNonVoidMethodPolicy} for classes that are Java records; if the
      * {@code BeansWrapper#BeansWrapper(Version) incompatibleImprovements} of the object wrapper is at least 2.3.33,
-     * then this defaults to {@link ZeroArgumentNonVoidMethodPolicy#BOTH_PROPERTY_AND_METHOD}, otherwise this defaults
-     * to {@link ZeroArgumentNonVoidMethodPolicy#METHOD_ONLY}.
+     * then this defaults to
+     * {@link ZeroArgumentNonVoidMethodPolicy#BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD},
+     * otherwise this defaults to {@link ZeroArgumentNonVoidMethodPolicy#METHOD_ONLY}.
      *
      * <p>Note that methods in this class are inherited by {@link DefaultObjectWrapper}, which is what you normally use.
      *
@@ -1949,12 +1947,12 @@
                     throw new BugException("Failed to create PropertyDescriptor for " + m, e);
                 }
                 methodInsteadOfPropertyValueBeforeCall = appliedZeroArgumentNonVoidMethodPolicy ==
-                        ZeroArgumentNonVoidMethodPolicy.BOTH_PROPERTY_AND_METHOD;
+                        ZeroArgumentNonVoidMethodPolicy.BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD;
             } else {
                 exposeAsProperty = null;
                 methodInsteadOfPropertyValueBeforeCall = false;
             }
-            exposeMethodAs = appliedZeroArgumentNonVoidMethodPolicy != ZeroArgumentNonVoidMethodPolicy.PROPERTY_ONLY
+            exposeMethodAs = appliedZeroArgumentNonVoidMethodPolicy != ZeroArgumentNonVoidMethodPolicy.PROPERTY_ONLY_UNLESS_BEAN_PROPERTY_READ_METHOD
                     ? m.getName() : null;
             methodShadowsProperty = true;
             replaceExistingProperty = false;
diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java b/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
index e69cf7a..3d8337c 100644
--- a/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
+++ b/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
@@ -252,6 +252,8 @@
     }
 
     /**
+     * Getter pair of {@link #setDefaultZeroArgumentNonVoidMethodPolicy}.
+     *
      * @since 2.3.33
      */
     public ZeroArgumentNonVoidMethodPolicy getDefaultZeroArgumentNonVoidMethodPolicy() {
@@ -270,6 +272,8 @@
     }
 
     /**
+     * Getter pair of {@link #setRecordZeroArgumentNonVoidMethodPolicy}.
+     *
      * @since 2.3.33
      */
     public ZeroArgumentNonVoidMethodPolicy getRecordZeroArgumentNonVoidMethodPolicy() {
@@ -287,6 +291,9 @@
         classIntrospectorBuilder.setRecordZeroArgumentNonVoidMethodPolicy(recordZeroArgumentNonVoidMethodPolicy);
     }
 
+    /**
+     * Getter pair of {@link #setMethodAppearanceFineTuner}
+     */
     public MethodAppearanceFineTuner getMethodAppearanceFineTuner() {
         return classIntrospectorBuilder.getMethodAppearanceFineTuner();
     }
@@ -295,6 +302,8 @@
      * See {@link BeansWrapper#setMethodAppearanceFineTuner(MethodAppearanceFineTuner)}; additionally,
      * note that currently setting this to non-{@code null} will disable class introspection cache sharing, unless
      * the value implements {@link SingletonCustomizer}.
+     *
+     * <p>Note that methods in this class are inherited by {@link DefaultObjectWrapperBuilder}, which is what you normally use.
      */
     public void setMethodAppearanceFineTuner(MethodAppearanceFineTuner methodAppearanceFineTuner) {
         classIntrospectorBuilder.setMethodAppearanceFineTuner(methodAppearanceFineTuner);
diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospector.java b/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospector.java
index b223868..45f76de 100644
--- a/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospector.java
+++ b/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospector.java
@@ -203,7 +203,7 @@
         if (recordAware && _JavaVersions.JAVA_16 == null) {
             throw new IllegalArgumentException(
                     "defaultZeroArgumentNonVoidMethodPolicy != recordZeroArgumentNonVoidMethodPolicy, " +
-                    "but Java 16 support is not available.");
+                    "but record support is not available (as Java 16 support is not available).");
         }
         this.incompatibleImprovements = builder.getIncompatibleImprovements();
 
@@ -316,7 +316,7 @@
 
         if (introspData.size() > 1) {
             return introspData;
-        } else if (introspData.size() == 0) {
+        } else if (introspData.isEmpty()) {
             return Collections.emptyMap();
         } else { // map.size() == 1
             Entry<Object, Object> e = introspData.entrySet().iterator().next();
diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java b/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
index ea75987..7ff8707 100644
--- a/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
+++ b/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
@@ -77,7 +77,8 @@
         treatDefaultMethodsAsBeanMembers = incompatibleImprovements.intValue() >= _VersionInts.V_2_3_26;
         defaultZeroArgumentNonVoidMethodPolicy = ZeroArgumentNonVoidMethodPolicy.METHOD_ONLY;
         recordZeroArgumentNonVoidMethodPolicy = incompatibleImprovements.intValue() >= _VersionInts.V_2_3_33 && _JavaVersions.JAVA_16 != null
-                ? ZeroArgumentNonVoidMethodPolicy.BOTH_PROPERTY_AND_METHOD : ZeroArgumentNonVoidMethodPolicy.METHOD_ONLY;
+                ? ZeroArgumentNonVoidMethodPolicy.BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD
+                : defaultZeroArgumentNonVoidMethodPolicy;
         memberAccessPolicy = DefaultMemberAccessPolicy.getInstance(this.incompatibleImprovements);
     }
 
@@ -166,6 +167,8 @@
     }
 
     /**
+     * The getter pair of {@link #setDefaultZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)}.
+     *
      * @since 2.3.33
      */
     public ZeroArgumentNonVoidMethodPolicy getDefaultZeroArgumentNonVoidMethodPolicy() {
@@ -173,6 +176,10 @@
     }
 
     /**
+     * Sets the {@link ZeroArgumentNonVoidMethodPolicy} used for classes that are not records (or any other special
+     * cases we add support for later).
+     * The default value is {@link ZeroArgumentNonVoidMethodPolicy#METHOD_ONLY}.
+     *
      * @since 2.3.33
      */
     public void setDefaultZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy defaultZeroArgumentNonVoidMethodPolicy) {
@@ -181,6 +188,8 @@
     }
 
     /**
+     * The getter pair of {@link #setRecordZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)}.
+     *
      * @since 2.3.33
      */
     public ZeroArgumentNonVoidMethodPolicy getRecordZeroArgumentNonVoidMethodPolicy() {
@@ -188,6 +197,14 @@
     }
 
     /**
+     * Sets the {@link ZeroArgumentNonVoidMethodPolicy} used for records.
+     * The default value is
+     * {@link ZeroArgumentNonVoidMethodPolicy#BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD} if
+     * {@link #getIncompatibleImprovements()} is at least 2.3.33, and we are on Java 16 or later, otherwise
+     * it's {@link ZeroArgumentNonVoidMethodPolicy#METHOD_ONLY}.
+     *
+     * @see #setDefaultZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)  
+     *
      * @since 2.3.33
      */
     public void setRecordZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy recordZeroArgumentNonVoidMethodPolicy) {
@@ -195,6 +212,11 @@
         this.recordZeroArgumentNonVoidMethodPolicy = recordZeroArgumentNonVoidMethodPolicy;
     }
 
+    /**
+     * Get getter pair of {@link #setMemberAccessPolicy(MemberAccessPolicy)}
+     *
+     * @since 2.3.30
+     */
     public MemberAccessPolicy getMemberAccessPolicy() {
         return memberAccessPolicy;
     }
diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java b/freemarker-core/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
index 6d7abdb..e4e3a09 100644
--- a/freemarker-core/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
+++ b/freemarker-core/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
@@ -36,11 +36,16 @@
  * {@link BeansWrapperBuilder#setMemberAccessPolicy(MemberAccessPolicy)} (or if you use {@link DefaultObjectWrapper},
  * with {@link DefaultObjectWrapperBuilder#setMemberAccessPolicy(MemberAccessPolicy)}).
  *
- * <p>As {@link BeansWrapper}, and its subclasses like {@link DefaultObjectWrapper}, only discover public
+ * <p>As {@link BeansWrapper}, and its subclasses, like {@link DefaultObjectWrapper}, only discover public
  * members, it's pointless to whitelist non-public members. (Also, while public members declared in non-public classes
  * are discovered by {@link BeansWrapper}, Java reflection will not allow accessing those normally, so generally it's
  * not useful to whitelist those either.)
  *
+ * <p>Note {@link BeansWrapper}, and its subclasses, like {@link DefaultObjectWrapper}, also have an
+ * {@link BeansWrapper#setExposureLevel(int) exposureLevel} a setting that's applied before the
+ * {@link MemberAccessPolicy}, also, with {@link BeansWrapper#EXPOSE_ALL} the {@link MemberAccessPolicy} will be
+ * ignored.
+ *
  * <p>Note that if you add {@link TemplateModel}-s directly to the data-model, those are not wrapped by the
  * {@link ObjectWrapper} (from {@link Environment#getObjectWrapper()}), and so the {@link MemberAccessPolicy} won't
  * affect those.
diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java b/freemarker-core/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java
index 8d0ccd8..88d1682 100644
--- a/freemarker-core/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java
+++ b/freemarker-core/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java
@@ -24,11 +24,13 @@
 
 import freemarker.ext.beans.BeansWrapper.MethodAppearanceDecision;
 import freemarker.ext.beans.BeansWrapper.MethodAppearanceDecisionInput;
+import freemarker.template.MethodCallAwareTemplateHashModel;
 
 /**
- * Used for customizing how the methods are visible from templates, via
+ * Used for customizing how the Java methods are visible from templates, via
  * {@link BeansWrapper#setMethodAppearanceFineTuner(MethodAppearanceFineTuner)}.
- * The object that implements this should also implement {@link SingletonCustomizer} whenever possible.
+ * The object that implements this should also implement {@link SingletonCustomizer} whenever possible, to allow reusing
+ * the class introspection cache in more situations.
  * 
  * @since 2.3.21
  */
@@ -52,24 +54,24 @@
      *     {@link #process} is not called for those.</li>
      *   <li>Show the method with a different name in the data-model than its
      *     real name by calling
-     *     {@link MethodAppearanceDecision#setExposeMethodAs(String)}
-     *     with non-{@code null} parameter. Also, if set to {@code null}, the method won't be exposed.
-     *     The default is the name of the method. Note that if {@code methodInsteadOfPropertyValueBeforeCall} is
-     *     {@code true}, the method is not exposed if the method name set here is the same as the name of the property
-     *     set for this method with {@link MethodAppearanceDecision#setExposeAsProperty(PropertyDescriptor)}.
+     *     {@link MethodAppearanceDecision#setExposeMethodAs(String)} with non-{@code null} parameter. (If set to
+     *     {@code null}, then the method won't be exposed.) The default is the real name of the method.
+     *     Note that if {@code methodInsteadOfPropertyValueBeforeCall} is {@code true}, the method is not exposed if the
+     *     method name set here is the same as the name of the property set for this method with
+     *     {@link MethodAppearanceDecision#setExposeAsProperty(PropertyDescriptor)}.
      *   <li>Create a fake JavaBean property for this method by calling
      *     {@link MethodAppearanceDecision#setExposeAsProperty(PropertyDescriptor)}.
      *     For example, if you have {@code int size()} in a class, but you
-     *     want it to be accessed from the templates as {@code obj.size},
-     *     rather than as {@code obj.size()}, you can do that with this
-     *     (but remember calling
-     *     {@link MethodAppearanceDecision#setMethodShadowsProperty(boolean)
-     *     setMethodShadowsProperty(false)} as well, if the method name is exactly
-     *     the same as the property name).
+     *     want it to be accessed from the templates as {@code obj.size} (like a JavaBean property),
+     *     rather than as {@code obj.size()}, you can do that with this (but remember calling
+     *     {@link MethodAppearanceDecision#setMethodShadowsProperty(boolean) setMethodShadowsProperty(false)} as well,
+     *     if the method name is exactly the same as the property name).
      *     The default is {@code null}, which means that no fake property is
      *     created for the method. You need not, and shouldn't set this
      *     to non-{@code null} for the property read (get/is) methods of real JavaBeans
-     *     properties, as bean properties are not seen as methods, and are exposed independently of this mechanism.
+     *     properties, as bean properties are not treated as methods (hence the {@link MethodAppearanceFineTuner} is
+     *     irrelevant), and are exposed or not regardless of this mechanism (based on
+     *     {@link BeansWrapperBuilder#setExposureLevel(int)}).
      *     The property name in the {@link PropertyDescriptor} can be anything,
      *     but the method (or methods) in it must belong to the class that
      *     is given as the {@code clazz} parameter, or it must be inherited from
@@ -88,29 +90,22 @@
      *     exposed that as a property via {@link MethodAppearanceDecision#setExposeAsProperty(PropertyDescriptor)}. So
      *     far, you can access the property value from templates as {@code user.name}, but {@code user.name()} will
      *     fail, saying that you try to call a {@code String} (because you apply the {@code ()} operator on the result
-     *     of {@code user.name}). But with
+     *     of {@code user.name}, which is a {@code String}). But with
      *     {@link MethodAppearanceDecision#setMethodInsteadOfPropertyValueBeforeCall(boolean)} {@code true},
-     *     both {@code user.name}, and {@code user.name()} will do the same.
-     *     The default of this is influenced by
+     *     both {@code user.name}, and {@code user.name()} will do the same (which is possible because if {@code user}
+     *     is a {@link MethodCallAwareTemplateHashModel}, "name" can be resoled to different values in the two cases).
+     *     The default (initial) value of {@code methodInsteadOfPropertyValueBeforeCall} depends on
      *     {@link BeansWrapperConfiguration#setDefaultZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)},
-     *     {@link BeansWrapperConfiguration#setRecordZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)}.
-     *   <li>Prevent the method to hide a JavaBeans property (fake or real) of
-     *     the same name by calling
-     *     {@link MethodAppearanceDecision#setMethodShadowsProperty(boolean)}
-     *     with {@code false}. The default is {@code true}, so if you have
-     *     both a property and a method called "foo", then in the template
-     *     {@code myObject.foo} will return the method itself instead
-     *     of the property value, which is often undesirable.
+     *     and {@link BeansWrapperConfiguration#setRecordZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)}.
+     *   <li>Prevent the method to hide a JavaBeans property (fake or real) of the same name by calling
+     *     {@link MethodAppearanceDecision#setMethodShadowsProperty(boolean)} with {@code false}.
+     *     The default is {@code true}, so if you have both a property and a method called "foo", then in the template
+     *     {@code myObject.foo} will return the method itself instead of the property value, which is often undesirable.
      * </ul>
-     * 
-     * <p>Note that you can expose a Java method both as a method, and as a
-     * JavaBeans property on the same time, however you have to chose different
-     * names for them to prevent shadowing. 
-     * 
-     * @param in Describes the method about which the decision will have to be made.
+     *
+     * @param in Describes the method about which the decision will be made.
      *  
-     * @param out Stores how the method will be exposed in the
-     *   data-model after {@link #process} returns.
+     * @param out Stores how the method will be exposed in the data-model after {@link #process} returns.
      *   This is initialized so that it reflects the default
      *   behavior of {@link BeansWrapper}, so you don't have to do anything with this
      *   when you don't want to change the default behavior.
diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/ZeroArgumentNonVoidMethodPolicy.java b/freemarker-core/src/main/java/freemarker/ext/beans/ZeroArgumentNonVoidMethodPolicy.java
index 2864e1c..607857c 100644
--- a/freemarker-core/src/main/java/freemarker/ext/beans/ZeroArgumentNonVoidMethodPolicy.java
+++ b/freemarker-core/src/main/java/freemarker/ext/beans/ZeroArgumentNonVoidMethodPolicy.java
@@ -20,9 +20,12 @@
 package freemarker.ext.beans;
 
 import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.MethodCallAwareTemplateHashModel;
+import freemarker.template.ObjectWrapper;
+import freemarker.template.TemplateHashModel;
 
 /**
- * How to show 0 argument non-void public methods to templates, which are not standard Java Beans read methods.
+ * How to show 0 argument non-void public methods to templates.
  * Used in {@link BeansWrapper}, and therefore in {@link DefaultObjectWrapper}.
  * This policy doesn't apply to methods that Java Beans introspector discovers as a property read method (which
  * typically look like {@code getSomething()}, or {@code isSomething()}). It's only applicable to methods like
@@ -31,6 +34,7 @@
  * @see BeansWrapperConfiguration#setDefaultZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)
  * @see BeansWrapperConfiguration#setRecordZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)
  * @see BeansWrapper.MethodAppearanceDecision#setMethodInsteadOfPropertyValueBeforeCall(boolean)
+ * @see MethodCallAwareTemplateHashModel
  *
  * @since 2.3.33
  */
@@ -38,12 +42,22 @@
 
     /**
      * Both {@code obj.m}, and {@code obj.m()} gives back the value that the {@code m} Java method returns, and it's
-     * not possible to get the method itself.
+     * not possible to get the method itself. But, it's not applicable for Java Bean property read methods
+     * (like {@code int getX()}), which remain just simple methods (because the Java Bean property is visible regardless
+     * of the {@link ZeroArgumentNonVoidMethodPolicy}, like {@code obj.x}, for which the Java Bean property read method
+     * is {@link int getX()}).
      *
-     * <p>This is a parse-time trick that only works when the result of the dot operator is called immediately in a
-     * template (and therefore the dot operator knows that you will call the result of it). The practical reason for
-     * this feature is that the convention of having {@code SomeType something()} instead of
-     * {@code SomeType getSomething()} spreads in the Java ecosystem (and is a standard in some other JVM languages),
+     * <p>This is a parse-time trick that only works when the result of the dot operator (like {@code obj.m}), or of the
+     * square bracket key operator (like {@code obj["m"]}) is called immediately in a template (like {@code obj.m()}, or
+     * like {@code obj["m"]()}), and therefore the dot, or square bracket key operator knows that you will call the
+     * result of it. In such case, if the {@linkplain ObjectWrapper wrapped} {@code obj} implements
+     * {@link MethodCallAwareTemplateHashModel}, the operator will call
+     * {@link MethodCallAwareTemplateHashModel#getBeforeMethodCall(String)} instead of
+     * {@link TemplateHashModel#get(String)}. Also note that at least in 2.3.33 it's only done if the method call has
+     * 0 arguments.
+     *
+     * <p>The practical reason for this feature is that the convention of using {@code SomeType something()} instead
+     * of {@code SomeType getSomething()} spreads in the Java ecosystem (and is a standard in some other JVM languages),
      * and thus we can't tell anymore if {@code SomeType something()} just reads a value, and hence should be accessed
      * like {@code obj.something}, or it's more like an operation that has side effect, and therefore should be
      * accessed like {@code obj.something()}. So with allowing both, the template author is free to decide which is
@@ -51,15 +65,17 @@
      * before FreeMarker was aware of records (and hence that those methods are like property read methods), the
      * only way that worked was {@code obj.something()}, so to be more backward compatible, we have to support both.
      */
-    BOTH_PROPERTY_AND_METHOD,
+    BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD,
 
     /**
-     * Only {@code obj.m()} gives back the value, {@code obj.m} just gives the method itself.
+     * Only {@code obj.m()} gives back the value in a template, {@code obj.m} in a template just gives the method itself.
      */
     METHOD_ONLY,
 
     /**
-     * {@code obj.m} in gives back the value, and the method itself can't be get.
+     * {@code obj.m} in a template gives back the value, and you can't get the method itself. But, it's not applicable
+     * for Java Bean property read methods, which will remain normals methods, just like with
+     * {@link #BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD}.
      */
-    PROPERTY_ONLY
+    PROPERTY_ONLY_UNLESS_BEAN_PROPERTY_READ_METHOD
 }
diff --git a/freemarker-core/src/main/java/freemarker/template/MethodCallAwareTemplateHashModel.java b/freemarker-core/src/main/java/freemarker/template/MethodCallAwareTemplateHashModel.java
index a3e8056..97173f3 100644
--- a/freemarker-core/src/main/java/freemarker/template/MethodCallAwareTemplateHashModel.java
+++ b/freemarker-core/src/main/java/freemarker/template/MethodCallAwareTemplateHashModel.java
@@ -30,23 +30,33 @@
 
 /**
  * Adds an extra getter method to {@link TemplateHashModel} that can return different result than {@link #get(String)},
- * knowing that the result of it will be called as a method. At least as of 2.3.33, this is only utilized by the
- * template language for 0-argument non-void method calls directly after the dot operator and the key (like
- * {@code obj.m()}), or for the equivalent with square brackets ({@code obj["m"]()}). For example, if in the
- * template you have {@code someRecord.someComponent()}, and there {@code someRecord} was wrapped by the
- * {@link ObjectWrapper} into a {@link TemplateHashModel} that also implements this interface, then the dot operator
- * will call {@link #getBeforeMethodCall(String) getBeforeMethodCall("someComponent")}, rather than
+ * knowing that the result of it will be called as a method. At least as of 2.3.33, this is only used by the
+ * template language for 0-argument method calls that are <em>directly</em> after the dot operator and the key (like in
+ * {@code obj.m()}, where the "()" is directly after the key "m"), or for the equivalent of that with square brackets
+ * ({@code obj["m"]()}).
+ *
+ * <p>Background knowledge needed to understand this: In the FreeMarker template language, methods/functions are
+ * first class values (just like strings, numbers,etc.). Also, unlike in Java, there's no separate namespace for
+ * methods, and for the other field-like members. When you have {@code obj.m()} in a template, first, the dot operator
+ * gets the value for the key "m", and after that, and independently of that, the method call operator tries to call
+ * that value (and if it's not a method or function, that will fail). The dot operator, before 2.3.33, was never aware
+ * of what the value it gets will be used for (like will it be called, will it be printed, etc.). Now it can be.
+ *
+ * For example, if in the template you have {@code someRecord.someComponent()}, and there {@code someRecord} was wrapped
+ * by the {@link ObjectWrapper} into a {@link TemplateHashModel} that also implements this interface, then the dot
+ * operator will call {@link #getBeforeMethodCall(String) getBeforeMethodCall("someComponent")}, rather than
  * {@link #get(String) get("someComponent")}. This is needed to implement subtle features like
  * {@link BeansWrapper.MethodAppearanceDecision#setMethodInsteadOfPropertyValueBeforeCall(boolean)},
- * which is needed to implement {@link ZeroArgumentNonVoidMethodPolicy#BOTH_PROPERTY_AND_METHOD}.
+ * which is needed to implement
+ * {@link ZeroArgumentNonVoidMethodPolicy#BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD}.
  *
  * <p>While technically we could do the same for method calls with more the 0 arguments, as of 2.3.33 at least, we
- * don't want to generalize this to that case. The FreeMarker 2.x template language doesn't have separated namespace for
- * methods, so this is already a hack as is, but we had to address the issue with Java records (see that at
+ * don't want to generalize this to that case. This is a workaround we added to address the issue with accessing
+ * components in Java records, which are 0-argument methods (see that at
  * {@link BeansWrapper.MethodAppearanceDecision#setMethodInsteadOfPropertyValueBeforeCall(boolean)}).
  *
  * <p>Objects wrapped with {@link BeansWrapper}, and hence with {@link DefaultObjectWrapper}, will implement this
- * interface, when they are "generic" objects (that is, when they are not classes with special wrapper, like
+ * interface when they are "generic" objects (that is, when they are not classes with special wrapper, like
  * {@link Map}-s, {@link Collection}-s, {@link Number}-s, etc.).
  *
  * @since 2.3.33
@@ -54,22 +64,23 @@
 public interface MethodCallAwareTemplateHashModel extends TemplateHashModel {
 
     /**
-     * This is called instead of {@link #get(String)}, if we know that the return value should be callable like a
-     * method. The advantage of this is that we can coerce the value to a method when desirable, and otherwise can give
+     * This is called instead of {@link #get(String)} if we know that the return value should be callable like a method.
+     * The advantage of this is that we can coerce the value to a method when desirable, and otherwise can give
      * a more specific error message in the resulting exception than the standard {@link NonMethodException} would.
      *
      * @param key
      *      Same as for {@link #get(String)}
      *
      * @return
-     *      Same as for just like {@link #get(String)}, except it should return a
-     *      {@link TemplateMethodModelEx}, or a {@link TemplateMethodModel}, or in very rare case a {@link Macro}
-     *      that was created with the {@code function} directive. Or, {@code null} in the same case as
+     *      Same as for {@link #get(String)}, except it should return a {@link TemplateMethodModel}
+     *      (a {@link TemplateMethodModelEx} if possible), or in very rare case a {@link Macro} that was created with
+     *      the {@code function} directive. Or, {@code null} in the same case as
      *      {@link #get(String)}. The method should never return something that's not callable in the template language
-     *      as a method or function.
+     *      as a method or function; the return type is {@link TemplateModel} because that's the most specific common
+     *      super-interface of {@link TemplateMethodModel}, and {@link Macro}.
      *
      * @throws ShouldNotBeGetAsMethodException
-     *      If the value for the given key exists, but it shouldn't be coerced something callable as a method. This will
+     *      If the value for the given key exists, but shouldn't be coerced to something callable as a method. This will
      *      be converted to {@link NonMethodException} by the engine, but in this exception you can optionally give a
      *      more specific explanation, and that will be added to the resulting {@link NonMethodException} as a hint to
      *      the user.
@@ -79,6 +90,8 @@
 
     /**
      * Thrown by {@link #getBeforeMethodCall(String)}; see there.
+     *
+     * @since 2.3.33
      */
     final class ShouldNotBeGetAsMethodException extends Exception {
         private final TemplateModel actualValue;
diff --git a/freemarker-core/src/main/resource-templates/freemarker/version.properties b/freemarker-core/src/main/resource-templates/freemarker/version.properties
index cecfdf7..aa63fee 100644
--- a/freemarker-core/src/main/resource-templates/freemarker/version.properties
+++ b/freemarker-core/src/main/resource-templates/freemarker/version.properties
@@ -56,11 +56,11 @@
 #   continue working without modification or recompilation.
 # - When the major version number is increased, major backward
 #   compatibility violations are allowed, but still should be avoided.
-version=2.3.33-nightly
+version=2.3.33
 # This exists as for Maven we use "-SNAPSHOT" for nightly releases,
 # and no _nightly. For final releases it's the
 # same as "version".
-mavenVersion=2.3.33-SNAPSHOT
+mavenVersion=2.3.33
 
 # Version string that conforms to OSGi
 # ------------------------------------
@@ -73,7 +73,7 @@
 #   2.4.0.rc01
 #   2.4.0.pre01
 #   2.4.0.nightly
-versionForOSGi=2.3.33.nightly
+versionForOSGi=2.3.33.stable
 
 # Version string that conforms to legacy MF
 # -----------------------------------------
@@ -92,7 +92,7 @@
 # "97 denotes "nightly", 98 denotes "pre", 99 denotes "rc" build.
 # In general, for the nightly/preview/rc Y of version 2.X, the versionForMf is
 # 2.X-1.(99|98).Y. Note the X-1.
-versionForMf=2.3.32.97
+versionForMf=2.3.33
 
 
 isGAECompliant=true
diff --git a/freemarker-core16/src/test/java/freemarker/ext/beans/TestZeroArgumentNonVoidMethodPolicy.java b/freemarker-core16/src/test/java/freemarker/ext/beans/TestZeroArgumentNonVoidMethodPolicy.java
index ce61d01..63f6048 100644
--- a/freemarker-core16/src/test/java/freemarker/ext/beans/TestZeroArgumentNonVoidMethodPolicy.java
+++ b/freemarker-core16/src/test/java/freemarker/ext/beans/TestZeroArgumentNonVoidMethodPolicy.java
@@ -75,7 +75,7 @@
                     () -> {
                         DefaultObjectWrapper beansWrapper = new DefaultObjectWrapper(VERSION_2_3_32);
                         beansWrapper.setRecordZeroArgumentNonVoidMethodPolicy(
-                                ZeroArgumentNonVoidMethodPolicy.BOTH_PROPERTY_AND_METHOD);
+                                ZeroArgumentNonVoidMethodPolicy.BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD);
                         return beansWrapper;
                     },
                     cacheTopLevelVars);
@@ -90,7 +90,8 @@
             setupDataModel(
                     () -> {
                         DefaultObjectWrapper beansWrapper = new DefaultObjectWrapper(VERSION_2_3_32);
-                        beansWrapper.setRecordZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy.PROPERTY_ONLY);
+                        beansWrapper.setRecordZeroArgumentNonVoidMethodPolicy(
+                                ZeroArgumentNonVoidMethodPolicy.PROPERTY_ONLY_UNLESS_BEAN_PROPERTY_READ_METHOD);
                         return beansWrapper;
                     },
                     cacheTopLevelVars);
@@ -105,7 +106,8 @@
             setupDataModel(
                     () -> {
                         DefaultObjectWrapper beansWrapper = new DefaultObjectWrapper(VERSION_2_3_33);
-                        beansWrapper.setRecordZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy.PROPERTY_ONLY);
+                        beansWrapper.setRecordZeroArgumentNonVoidMethodPolicy(
+                                ZeroArgumentNonVoidMethodPolicy.PROPERTY_ONLY_UNLESS_BEAN_PROPERTY_READ_METHOD);
                         return beansWrapper;
                     },
                     cacheTopLevelVars);
@@ -120,7 +122,8 @@
             setupDataModel(
                     () -> {
                         DefaultObjectWrapper beansWrapper = new DefaultObjectWrapper(VERSION_2_3_32);
-                        beansWrapper.setDefaultZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy.PROPERTY_ONLY);
+                        beansWrapper.setDefaultZeroArgumentNonVoidMethodPolicy(
+                                ZeroArgumentNonVoidMethodPolicy.PROPERTY_ONLY_UNLESS_BEAN_PROPERTY_READ_METHOD);
                         return beansWrapper;
                     },
                     cacheTopLevelVars);
@@ -136,7 +139,7 @@
                     () -> {
                         DefaultObjectWrapper beansWrapper = new DefaultObjectWrapper(VERSION_2_3_33);
                         beansWrapper.setDefaultZeroArgumentNonVoidMethodPolicy(
-                                ZeroArgumentNonVoidMethodPolicy.BOTH_PROPERTY_AND_METHOD);
+                                ZeroArgumentNonVoidMethodPolicy.BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD);
                         return beansWrapper;
                     },
                     cacheTopLevelVars);
@@ -149,7 +152,10 @@
     public void testSettings() throws TemplateException, IOException {
             getConfiguration().setSetting(
                     "objectWrapper",
-                    "DefaultObjectWrapper(2.3.33, defaultZeroArgumentNonVoidMethodPolicy=freemarker.ext.beans.ZeroArgumentNonVoidMethodPolicy.BOTH_PROPERTY_AND_METHOD)");
+                    "DefaultObjectWrapper(2.3.33, "
+                            + "defaultZeroArgumentNonVoidMethodPolicy="
+                            + "freemarker.ext.beans.ZeroArgumentNonVoidMethodPolicy"
+                            + ".BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD)");
             setupDataModel(() -> getConfiguration().getObjectWrapper(), false);
             assertRecIsBothPropertyAndMethod();
             assertNrcIsBothPropertyAndMethod();
diff --git a/freemarker-manual/src/main/docgen/en_US/book.xml b/freemarker-manual/src/main/docgen/en_US/book.xml
index 1533267..0bcb88a 100644
--- a/freemarker-manual/src/main/docgen/en_US/book.xml
+++ b/freemarker-manual/src/main/docgen/en_US/book.xml
@@ -30114,13 +30114,12 @@
       <section xml:id="versions_2_3_33">
         <title>2.3.33</title>
 
-        <para>Release date: [TODO]</para>
+        <para>Release date: 2024-05-08 + release process</para>
 
         <para>Please note that with this version the minimum required Java
-        version was increased from Java 7 to Java 8. Also for the few who
-        relly on Servlet and/or JSP support, the minimum is now increased to
-        Servlet 3.0, and JSP 2.2 (which are still very old versions from
-        2011).</para>
+        version was increased from Java 7 to Java 8. Also for the few who rely
+        on Servlet and/or JSP support, the minimum is now increased to Servlet
+        3.0, and JSP 2.2 (which are still very old versions from 2011).</para>
 
         <section>
           <title>Changes on the FTL side</title>
@@ -30269,8 +30268,9 @@
               <literal>ObjectWrapper</literal>, then set its
               <literal>incompatible_improvements</literal>, or just its
               <literal>recordZeroArgumentNonVoidMethodPolicy</literal>
-              property to <literal>BOTH_PROPERTY_AND_METHOD</literal>). If in
-              a Java record you have something like <literal>int
+              property to
+              <literal>BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD</literal>).
+              If in a Java record you have something like <literal>int
               price()</literal>, earlier you could only read the value in
               templates as <literal>obj.price()</literal>. With this
               improvement <literal>obj.price</literal> will do the same (and
@@ -30305,8 +30305,9 @@
                   Each has enum type
                   <literal>freemarker.ext.beans.ZeroArgumentNonVoidMethodPolicy</literal>,
                   that can be <literal>METHOD_ONLY</literal>,
-                  <literal>PROPERTY_ONLY</literal>, or
-                  <literal>BOTH_PROPERTY_AND_METHOD</literal>.
+                  <literal>PROPERTY_ONLY_UNLESS_BEAN_PROPERTY_READ_METHOD</literal>,
+                  or
+                  <literal>BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD</literal>.
                   Therefore:</para>
 
                   <itemizedlist>
@@ -30324,7 +30325,9 @@
                       <para>For records, you can enforce proper style with
                       setting
                       <literal>recordZeroArgumentNonVoidMethodPolicy</literal>
-                      to <literal>PROPERTY_ONLY</literal>. The default with
+                      to
+                      <literal>PROPERTY_ONLY_UNLESS_BEAN_PROPERTY_READ_METHOD</literal>.
+                      The default with
                       <literal>incompatible_improvements</literal> 2.3.33 is
                       more lenient, as there using <literal>()</literal> is
                       allowed (for backward compatibility, and because people
@@ -30345,7 +30348,7 @@
                   instead of
                   <literal>TemplateHashModel.get("price")</literal>. This is
                   needed for
-                  <literal>ZeroArgumentNonVoidMethodPolicy.BOTH_PROPERTY_AND_METHOD</literal>
+                  <literal>ZeroArgumentNonVoidMethodPolicy.BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD</literal>
                   to work.</para>
                 </listitem>