Merge pull request #312 from aguibert/property-type-mismatch

 Apply adapters based on property read/write type
diff --git a/README.md b/README.md
index b599345..c6d7f2e 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,10 @@
+# Eclipse Yasson
+
+[![Maven Central](https://img.shields.io/maven-central/v/org.eclipse/yasson.svg?label=Maven%20Central)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.eclipse%22%20a%3A%22yasson%22)
+[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/https/oss.sonatype.org/org.eclipse/yasson.svg)](https://oss.sonatype.org/content/repositories/snapshots/org/eclipse/yasson/)
 [![Build Status](https://travis-ci.org/eclipse-ee4j/yasson.svg?branch=master)](https://travis-ci.org/eclipse-ee4j/yasson)
 [![License](https://img.shields.io/badge/License-EPL%201.0-green.svg)](https://opensource.org/licenses/EPL-1.0)
-[![Maven Central](https://img.shields.io/maven-central/v/org.eclipse/yasson.svg?label=Maven%20Central)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.eclipse%22%20a%3A%22yasson%22)
 
-# Yasson
 Yasson is a Java framework which provides a standard binding layer between Java classes and JSON documents. This is similar to what JAXB is doing in the XML world. Yasson is an official reference implementation of JSON Binding ([JSR-367](https://jcp.org/en/jsr/detail?id=367)).
 
 It defines a **default mapping** algorithm for converting existing Java classes to JSON suitable for the most cases:
diff --git a/src/main/java/org/eclipse/yasson/internal/ClassParser.java b/src/main/java/org/eclipse/yasson/internal/ClassParser.java
index 832a99f..2edcfc2 100644
--- a/src/main/java/org/eclipse/yasson/internal/ClassParser.java
+++ b/src/main/java/org/eclipse/yasson/internal/ClassParser.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2019 Oracle and/or its affiliates. All rights reserved.
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
  * which accompanies this distribution.
@@ -29,6 +29,7 @@
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.ArrayList;
@@ -173,7 +174,7 @@
         for (Method method : declaredMethods) {
             String name = method.getName();
             //isBridge method filters out methods inherited from interfaces
-            if (!isPropertyMethod(method) || method.isBridge()) {
+            if (!isPropertyMethod(method) || method.isBridge() || isSpecialCaseMethod(clazz, method)) {
                 continue;
             }
             final String propertyName = toPropertyMethod(name);
@@ -181,6 +182,25 @@
             Property property = registerMethod(propertyName, method, classElement, classProperties);
         }
     }
+    
+    /**
+     * Filter out certain methods that get forcibly added to some classes.
+     * For example the public groovy.lang.MetaClass X.getMetaClass() method from Groovy classes
+     */
+    private boolean isSpecialCaseMethod(Class<?> clazz, Method m) {
+        if (!Modifier.isPublic(m.getModifiers()) || Modifier.isStatic(m.getModifiers()) || m.isSynthetic())
+            return false;
+        // Groovy objects will have public groovy.lang.MetaClass X.getMetaClass()
+        // which causes an infinite loop in serialization
+        if (m.getName().equals("getMetaClass") && 
+                m.getReturnType().getCanonicalName().equals("groovy.lang.MetaClass"))
+            return true;
+        // WELD proxy objects will have 'public org.jboss.weld
+        if (m.getName().equals("getMetadata") && 
+                m.getReturnType().getCanonicalName().equals("org.jboss.weld.proxy.WeldClientProxy$Metadata"))
+            return true;
+        return false;
+    }
 
     private boolean isGetter(Method m) {
         return (m.getName().startsWith(GET_PREFIX) || m.getName().startsWith(IS_PREFIX)) && m.getParameterCount() == 0;
diff --git a/src/main/java/org/eclipse/yasson/internal/properties/MessageKeys.java b/src/main/java/org/eclipse/yasson/internal/properties/MessageKeys.java
index 3336dda..95fb57e 100644
--- a/src/main/java/org/eclipse/yasson/internal/properties/MessageKeys.java
+++ b/src/main/java/org/eclipse/yasson/internal/properties/MessageKeys.java
@@ -68,6 +68,7 @@
     INCOMPATIBLE_FACTORY_CREATOR_RETURN_TYPE("incompatibleFactoryCreatorReturnType"),
     MULTIPLE_JSONB_CREATORS("multipleJsonbCreators"),
     INTERNAL_ERROR("internalError"),
+    SERIALIZE_PROPERTY_ERROR("serializePropertyError"),
     DESERIALIZE_VALUE_ERROR("deserializeValueError"),
     PARSING_NUMBER("parsingNumber"),
     UNKNOWN_BINARY_DATA_STRATEGY("unknownBinaryDataStrategy"),
diff --git a/src/main/java/org/eclipse/yasson/internal/serializer/AbstractValueTypeSerializer.java b/src/main/java/org/eclipse/yasson/internal/serializer/AbstractValueTypeSerializer.java
index 242a3e9..ab81280 100644
--- a/src/main/java/org/eclipse/yasson/internal/serializer/AbstractValueTypeSerializer.java
+++ b/src/main/java/org/eclipse/yasson/internal/serializer/AbstractValueTypeSerializer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
  * which accompanies this distribution.
diff --git a/src/main/java/org/eclipse/yasson/internal/serializer/ObjectSerializer.java b/src/main/java/org/eclipse/yasson/internal/serializer/ObjectSerializer.java
index 2443d5d..c8350aa 100644
--- a/src/main/java/org/eclipse/yasson/internal/serializer/ObjectSerializer.java
+++ b/src/main/java/org/eclipse/yasson/internal/serializer/ObjectSerializer.java
@@ -16,7 +16,10 @@
 import org.eclipse.yasson.internal.ReflectionUtils;
 import org.eclipse.yasson.internal.model.ClassModel;
 import org.eclipse.yasson.internal.model.PropertyModel;
+import org.eclipse.yasson.internal.properties.MessageKeys;
+import org.eclipse.yasson.internal.properties.Messages;
 
+import javax.json.bind.JsonbException;
 import javax.json.bind.serializer.JsonbSerializer;
 import javax.json.bind.serializer.SerializationContext;
 import javax.json.stream.JsonGenerator;
@@ -55,9 +58,15 @@
 
     @Override
     protected void serializeInternal(T object, JsonGenerator generator, SerializationContext ctx) {
-        final PropertyModel[] allProperties = ((Marshaller) ctx).getMappingContext().getOrCreateClassModel(object.getClass()).getSortedProperties();
+        final PropertyModel[] allProperties = ((Marshaller) ctx).getMappingContext()
+                .getOrCreateClassModel(object.getClass()).getSortedProperties();
         for (PropertyModel model : allProperties) {
-            marshallProperty(object, generator, ctx, model);
+            try {
+                marshallProperty(object, generator, ctx, model);
+            } catch (Exception e) {
+                throw new JsonbException(Messages.getMessage(MessageKeys.SERIALIZE_PROPERTY_ERROR, model.getWriteName(),
+                        object.getClass().getCanonicalName()), e);
+            }
         }
     }
 
diff --git a/src/main/resources/yasson-messages.properties b/src/main/resources/yasson-messages.properties
index 59b8a68..b0efc2b 100644
--- a/src/main/resources/yasson-messages.properties
+++ b/src/main/resources/yasson-messages.properties
@@ -63,6 +63,7 @@
 incompatibleFactoryCreatorReturnType=Return type of creator {0} must be of type {1}.
 multipleJsonbCreators=More than one @JsonbCreator declared in class {0}.
 internalError=Internal error: {0}
+serializePropertyError=Unable to serialize property ''{0}'' from {1}
 deserializeValueError=Error deserialize JSON value into type: {0}.
 parsingNumber=Error parsing number {0} with format {1}.
 unknownBinaryDataStrategy=Unknown binary data strategy: {0}
diff --git a/src/test/java/org/eclipse/yasson/Assertions.java b/src/test/java/org/eclipse/yasson/Assertions.java
new file mode 100644
index 0000000..509ba75
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/Assertions.java
@@ -0,0 +1,67 @@
+package org.eclipse.yasson;
+
+import static org.junit.Assert.fail;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import javax.json.bind.JsonbException;
+
+public class Assertions {
+	
+	/**
+	 * Asserts that the given operation will fail with a JsonbException
+	 * @param operation The operation that is expected to fail
+	 */
+	public static void shouldFail(Supplier<?> operation) {
+		shouldFail(operation, JsonbException.class, msg -> true);
+	}
+	
+	public static void shouldFail(Runnable operation) {
+		shouldFail(() -> {
+			operation.run();
+			return null;
+		});
+	}
+	
+	/**
+	 * Asserts that the given operation will fail with a JsonbException
+	 * @param operation The operation that is expected to fail
+	 * @param checkExceptionMessage Any checks that should be made on the exception message. For example, ensuring the exception
+	 * includes a specific token. 
+	 */
+	public static void shouldFail(Supplier<?> operation, Function<String,Boolean> checkExceptionMessage) {
+		shouldFail(operation, JsonbException.class, checkExceptionMessage);
+	}
+
+	/**
+	 * Asserts that the given operation will fail
+	 * @param operation The operation that is expected to fail
+	 * @param expectedType The expected exception type to receive when evaluating the operation
+	 * @param checkExceptionMessage Any checks that should be made on the exception message. For example, ensuring the exception
+	 * includes a specific token. 
+	 */
+	public static void shouldFail(Supplier<?> operation, Class<? extends Throwable> expectedType, Function<String,Boolean> checkExceptionMessage) {
+		try {
+			operation.get();
+			fail("The operation should have failed with a " + expectedType.getCanonicalName() + " but it succeeded.");
+		} catch (Throwable t) {
+			String fullErrorMessage = "";
+			for (Throwable current = t; current != null && current.getCause() != current; current = current.getCause()) {
+			    fullErrorMessage += current.getClass().getCanonicalName() + ": ";
+				fullErrorMessage += current.getMessage() + "\n";
+			}
+			if (expectedType.isAssignableFrom(t.getClass())) {
+				if (!checkExceptionMessage.apply(fullErrorMessage)) {
+					t.printStackTrace();
+					fail("Exception did not contain the proper content: " + fullErrorMessage);
+				}
+			} else {
+				t.printStackTrace();
+				fail("Expected to get an exception of " + expectedType + " but instead was " + t.getClass());
+			}
+		}
+		
+	}
+
+}
diff --git a/src/test/java/org/eclipse/yasson/customization/JsonbPropertyTest.java b/src/test/java/org/eclipse/yasson/customization/JsonbPropertyTest.java
index e35ed1a..4a350b6 100644
--- a/src/test/java/org/eclipse/yasson/customization/JsonbPropertyTest.java
+++ b/src/test/java/org/eclipse/yasson/customization/JsonbPropertyTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
  * which accompanies this distribution.
@@ -152,6 +152,22 @@
         }
 
     }
+    
+    @Test
+    public void testConflictingWithLowercaseStrategy() {
+    	// scenario raised by user here: https://github.com/eclipse-ee4j/yasson/issues/296
+    	Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_UNDERSCORES));
+    	assertEquals("{\"url\":\"http://foo.com\"}", 
+    			jsonb.toJson(new ConflictingIfLowercase()));
+    }
+    
+    public static class ConflictingIfLowercase {
+    	private String url = "foo.com";
+    	
+    	public String getURL() {
+    		return "http://" + url;
+    	}
+    }
 
     public static class NonConflictingProperties {
         private String doi;
diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/basic/NumberTest.java b/src/test/java/org/eclipse/yasson/defaultmapping/basic/NumberTest.java
index a2cbadc..16d5e5d 100644
--- a/src/test/java/org/eclipse/yasson/defaultmapping/basic/NumberTest.java
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/basic/NumberTest.java
@@ -14,6 +14,7 @@
 
 package org.eclipse.yasson.defaultmapping.basic;
 
+import static org.eclipse.yasson.Assertions.*;
 import org.eclipse.yasson.TestTypeToken;
 import org.eclipse.yasson.defaultmapping.basic.model.BigDecimalInNumber;
 import org.eclipse.yasson.defaultmapping.generics.model.ScalarValueWrapper;
@@ -26,10 +27,18 @@
 import javax.json.JsonWriter;
 import javax.json.bind.Jsonb;
 import javax.json.bind.JsonbBuilder;
+import javax.json.bind.JsonbException;
 import javax.json.stream.JsonGenerator;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.io.StringWriter;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
 
 /**
  * @author Roman Grigoriadi
@@ -207,5 +216,37 @@
                 "\"unsafeBigDecimalValue\":9223372036854775807}", w.toString());
 
     }
+    
+    public static class NumberContainer {
+    	public Double doubleProp;
+    	public Collection<Double> collectionProp;
+    	public Map<String,Double> mapProp;
+    }
+    
+    @Test
+    public void testSerializeInvalidDouble() {
+        shouldFail(() -> jsonb.toJson(Double.POSITIVE_INFINITY));
+
+        NumberContainer obj = new NumberContainer();
+        obj.doubleProp = Double.POSITIVE_INFINITY;
+        shouldFail(() -> jsonb.toJson(obj), msg -> msg.contains("doubleProp") && msg.contains("NumberContainer"));
+    }
+    
+    
+    @Test
+    public void testSerializeInvalidDoubleCollection() {
+        NumberContainer obj = new NumberContainer();
+        obj.collectionProp = Collections.singleton(Double.POSITIVE_INFINITY);
+        shouldFail(() -> jsonb.toJson(obj),
+                msg -> msg.contains("collectionProp") && msg.contains("NumberContainer"));
+    }
+
+    @Test
+    public void testSerializeInvalidDoubleMap() {
+        NumberContainer obj = new NumberContainer();
+        obj.mapProp = Collections.singletonMap("doubleKey", Double.POSITIVE_INFINITY);
+        shouldFail(() -> jsonb.toJson(obj),
+                msg -> msg.contains("mapProp") && msg.contains("NumberContainer"));
+    }
 
 }
diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/modifiers/ClassModifiersTest.java b/src/test/java/org/eclipse/yasson/defaultmapping/modifiers/ClassModifiersTest.java
index bf2a7b4..8bb67eb 100644
--- a/src/test/java/org/eclipse/yasson/defaultmapping/modifiers/ClassModifiersTest.java
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/modifiers/ClassModifiersTest.java
@@ -17,6 +17,7 @@
 import javax.json.bind.JsonbBuilder;
 import javax.json.bind.JsonbException;
 
+import org.eclipse.yasson.Assertions;
 import org.eclipse.yasson.defaultmapping.modifiers.model.ChildOfPackagePrivateParent;
 import org.eclipse.yasson.defaultmapping.modifiers.model.FieldModifiersClass;
 import org.junit.Before;
@@ -67,14 +68,9 @@
         NestedPackageChild child = new NestedPackageChild();
         child.id = 1;
         child.name = "SomeName";
-        try {
-            jsonb.toJson(child);
-            fail();
-        } catch (JsonbException ex) {
-            if (!(ex.getCause() instanceof IllegalAccessException)) {
-                fail();
-            }
-        }
+        Assertions.shouldFail(() -> jsonb.toJson(child),
+                msg -> msg.contains("Unable to serialize property 'id'") &&
+                msg.contains("java.lang.IllegalAccessException")); 
     }
 
     private class NestedPrivateParent {
@@ -90,14 +86,8 @@
         NestedPrivateChild child = new NestedPrivateChild();
         child.id = 1;
         child.name = "SomeName";
-        try {
-            jsonb.toJson(child);
-            fail();
-        } catch (JsonbException ex) {
-            if (!(ex.getCause() instanceof IllegalAccessException)) {
-                fail();
-            }
-        }
+        Assertions.shouldFail(() -> jsonb.toJson(child),
+                msg -> msg.contains("java.lang.IllegalAccessException"));
     }
 
 
@@ -114,14 +104,8 @@
         NestedStaticPackageChild child = new NestedStaticPackageChild();
         child.id = 1;
         child.name = "SomeName";
-        try {
-            jsonb.toJson(child);
-            fail();
-        } catch (JsonbException ex) {
-            if (!(ex.getCause() instanceof IllegalAccessException)) {
-                fail();
-            }
-        }
+        Assertions.shouldFail(() -> jsonb.toJson(child),
+                msg -> msg.contains("java.lang.IllegalAccessException"));
     }
 
     private static class NestedStaticPrivateParent {
@@ -137,14 +121,8 @@
         NestedStaticPrivateChild child = new NestedStaticPrivateChild();
         child.id = 1;
         child.name = "SomeName";
-        try {
-            jsonb.toJson(child);
-            fail();
-        } catch (JsonbException ex) {
-            if (!(ex.getCause() instanceof IllegalAccessException)) {
-                fail();
-            }
-        }
+        Assertions.shouldFail(() -> jsonb.toJson(child),
+                msg -> msg.contains("java.lang.IllegalAccessException"));
     }
 
 }
diff --git a/src/test/java/org/eclipse/yasson/documented/DocumentationExampleTest.java b/src/test/java/org/eclipse/yasson/documented/DocumentationExampleTest.java
new file mode 100644
index 0000000..ac4f751
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/documented/DocumentationExampleTest.java
@@ -0,0 +1,576 @@
+package org.eclipse.yasson.documented;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.json.bind.JsonbConfig;
+import javax.json.bind.adapter.JsonbAdapter;
+import javax.json.bind.annotation.JsonbCreator;
+import javax.json.bind.annotation.JsonbDateFormat;
+import javax.json.bind.annotation.JsonbNillable;
+import javax.json.bind.annotation.JsonbNumberFormat;
+import javax.json.bind.annotation.JsonbProperty;
+import javax.json.bind.annotation.JsonbTransient;
+import javax.json.bind.serializer.DeserializationContext;
+import javax.json.bind.serializer.JsonbDeserializer;
+import javax.json.bind.serializer.JsonbSerializer;
+import javax.json.bind.serializer.SerializationContext;
+import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonParser;
+
+import org.junit.Test;
+
+/**
+ * Contains tests from http://json-b.net/docs/user-guide.html
+ */
+public class DocumentationExampleTest {
+
+    public static class Dog {
+        public String name;
+        public int age;
+        public boolean bitable;
+    }
+
+    @Test
+    public void testMappingExample() {
+        // Create a dog instance
+        Dog dog = new Dog();
+        dog.name = "Falco";
+        dog.age = 4;
+        dog.bitable = false;
+
+        // Create Jsonb and serialize
+        Jsonb jsonb = JsonbBuilder.create();
+        String result = jsonb.toJson(dog);
+        assertEquals("{\"age\":4,\"bitable\":false,\"name\":\"Falco\"}", result);
+
+        // Deserialize back
+        dog = jsonb.fromJson("{\"name\":\"Falco\",\"age\":4,\"bites\":false}", Dog.class);
+        assertEquals("Falco", dog.name);
+        assertEquals(4, dog.age);
+        assertEquals(false, dog.bitable);
+    }
+
+    @Test
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public void testMappingCollection() {
+        Dog falco = new Dog();
+        falco.name = "Falco";
+        falco.age = 4;
+        Dog cassidy = new Dog();
+        cassidy.name = "Cassidy";
+        cassidy.age = 5;
+
+        // List of dogs
+        List dogs = new ArrayList();
+        dogs.add(falco);
+        dogs.add(cassidy);
+
+        // Create Jsonb and serialize
+        Jsonb jsonb = JsonbBuilder.create();
+        String result = jsonb.toJson(dogs);
+        assertEquals(
+                "[{\"age\":4,\"bitable\":false,\"name\":\"Falco\"},{\"age\":5,\"bitable\":false,\"name\":\"Cassidy\"}]",
+                result);
+
+        // We can also deserialize back into a raw collection, but since there is no way
+        // to infer a type here,
+        // the result will be a list of java.util.Map instances with string keys.
+        dogs = jsonb.fromJson(result, ArrayList.class);
+        assertEquals(2, dogs.size());
+        assertEquals("Falco", ((Map) dogs.get(0)).get("name"));
+        assertEquals("Cassidy", ((Map) dogs.get(1)).get("name"));
+        // assertEquals(4, ((Map) dogs.get(0)).get("age")); // TODO should these
+        // actually be BigDecimals?
+        // assertEquals(5, ((Map) dogs.get(1)).get("age"));
+    }
+
+    @SuppressWarnings("serial")
+    @Test
+    public void testMappingGenericCollection() {
+        Dog falco = new Dog();
+        falco.name = "Falco";
+        falco.age = 4;
+        Dog cassidy = new Dog();
+        cassidy.name = "Cassidy";
+        cassidy.age = 5;
+
+        // List of dogs
+        List<Dog> dogs = new ArrayList<>();
+        dogs.add(falco);
+        dogs.add(cassidy);
+
+        // Create Jsonb and serialize
+        Jsonb jsonb = JsonbBuilder.create();
+        String result = jsonb.toJson(dogs);
+        assertEquals(
+                "[{\"age\":4,\"bitable\":false,\"name\":\"Falco\"},{\"age\":5,\"bitable\":false,\"name\":\"Cassidy\"}]",
+                result);
+
+        // Deserialize back
+        dogs = jsonb.fromJson(result, new ArrayList<Dog>() {
+        }.getClass().getGenericSuperclass());
+        assertEquals(2, dogs.size());
+        assertEquals("Falco", dogs.get(0).name);
+        assertEquals("Cassidy", dogs.get(1).name);
+    }
+
+    @Test
+    public void testFormattedOutput() {
+        Dog pojo = new Dog();
+        pojo.name = "Falco";
+        pojo.age = 4;
+
+        // Create custom configuration with formatted output
+        JsonbConfig config = new JsonbConfig().withFormatting(true);
+
+        // Create Jsonb with custom configuration
+        Jsonb jsonb = JsonbBuilder.create(config);
+
+        // Use it!
+        String result = jsonb.toJson(pojo);
+        assertEquals("\n" + 
+                "{\n" + 
+                "    \"age\": 4,\n" + 
+                "    \"bitable\": false,\n" + 
+                "    \"name\": \"Falco\"\n" + 
+                "}", result);
+    }
+    
+    public static class Person1 {
+        @JsonbProperty("person-name")
+        public String name;
+        public String profession;
+    }
+    
+    @Test
+    public void testChangingPropertyNames1() {
+        Person1 p = new Person1();
+        p.name = "Jason Bourne";
+        p.profession = "Super Agent";
+        Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withFormatting(true));
+        String result = jsonb.toJson(p);
+        assertEquals("\n" +
+                "{\n" + 
+                "    \"person-name\": \"Jason Bourne\",\n" + 
+                "    \"profession\": \"Super Agent\"\n" + 
+                "}", result);
+    }
+    
+    public class Person2 {
+        private String name;
+        private String profession;
+
+        @JsonbProperty("person-name")
+        public String getName() {
+            return name;
+        }
+
+        public String getProfession() {
+            return profession;
+        }
+
+        // public setters ...
+        public void setName(String name) {
+            this.name = name;
+        }
+        public void setProfession(String profession) {
+            this.profession = profession;
+        }
+    }
+    
+    @Test
+    public void testChangingPropertyNames2() {
+        Person2 p = new Person2();
+        p.name = "Jason Bourne";
+        p.profession = "Super Agent";
+        Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withFormatting(true));
+        String result = jsonb.toJson(p);
+        assertEquals("\n" +
+                "{\n" + 
+                "    \"person-name\": \"Jason Bourne\",\n" + 
+                "    \"profession\": \"Super Agent\"\n" + 
+                "}", result);
+    }
+    
+    public static class Person3 {
+        private String name;
+
+        @JsonbProperty("name-to-write")
+        public String getName() {
+            return name;
+        }
+
+        @JsonbProperty("name-to-read")
+        public void setName(String name) {
+            this.name = name;
+        }
+    }
+    
+    @Test
+    public void testChangingPropertyNames3() {
+        Person3 p = new Person3();
+        p.name = "Jason Bourne";
+        Jsonb jsonb = JsonbBuilder.create();
+        String result = jsonb.toJson(p);
+        assertEquals("{\"name-to-write\":\"Jason Bourne\"}", result);
+        
+        String json = "{\"name-to-read\":\"Jason Bourne\"}";
+        Person3 after = jsonb.fromJson(json, Person3.class);
+        assertEquals("Jason Bourne", after.name);
+    }
+    
+    public static class Person4 { // TODO: a non-static class results in an NPE
+        @JsonbTransient
+        private String name;
+
+        private String profession;
+
+        // public getters/setters ...
+        public String getName() {
+            return name;
+        }
+        public void setName(String name) {
+            this.name = name;
+        }
+        public String getProfession() {
+            return this.profession;
+        }
+        public void setProfession(String profession) {
+            this.profession = profession;
+        }
+    }
+    
+    @Test
+    public void testIgnoringProperties() {
+        Person4 p = new Person4();
+        p.name = "Jason Bourne";
+        p.profession = "Super Agent";
+        Jsonb jsonb = JsonbBuilder.create();
+        String result = jsonb.toJson(p);
+        assertEquals("{\"profession\":\"Super Agent\"}", result);
+        
+        String json = "{\"profession\":\"Super Agent\"}";
+        Person4 after = jsonb.fromJson(json, Person4.class);
+        assertEquals("Super Agent", after.profession);
+        assertNull(after.name);
+    }
+    
+    @JsonbNillable
+    public class Person5 {
+        private String name;
+        private String profession;
+
+        // public getters/setters ...
+        public String getName() {
+            return name;
+        }
+        public void setName(String name) {
+            this.name = name;
+        }
+        public String getProfession() {
+            return profession;
+        }
+        public void setProfession(String profession) {
+            this.profession = profession;
+        }
+    }
+    
+    @Test
+    public void testNullHandling1() {
+        Person5 p = new Person5();
+        Jsonb jsonb = JsonbBuilder.create();
+        String result = jsonb.toJson(p);
+        assertEquals("{\"name\":null,\"profession\":null}", result);
+    }
+    
+    public class Person6 {
+        @JsonbProperty(nillable=true)
+        private String name;
+
+        private String profession;
+
+        // public getters/setters ...
+        public String getName() {
+            return name;
+        }
+        public void setName(String name) {
+            this.name = name;
+        }
+        public String getProfession() {
+            return profession;
+        }
+        public void setProfession(String profession) {
+            this.profession = profession;
+        }
+    }
+    
+    @Test
+    public void testNullHandling2() {
+        Person6 p = new Person6();
+        Jsonb jsonb = JsonbBuilder.create();
+        String result = jsonb.toJson(p);
+        assertEquals("{\"name\":null}", result);
+    }
+    
+    public static class Person {
+        public String name;
+        public String profession;
+    }
+
+    @Test
+    public void testNullHandling3() {
+        Person p = new Person();
+        Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withNullValues(true));
+        String result = jsonb.toJson(p);
+        assertEquals("{\"name\":null,\"profession\":null}", result);
+    }
+    
+    public static class Person8 { // TODO: obscure error here if non-static
+        public final String name;
+        public String profession;
+
+        @JsonbCreator
+        public Person8(@JsonbProperty("name") String name) {
+            this.name = name;
+        }
+    }
+    
+    @Test
+    public void testCustomInstantiation() {
+        Jsonb jsonb = JsonbBuilder.create();
+        Person8 p = jsonb.fromJson("{\"name\":\"Jason Bourne\"}", Person8.class);
+        assertEquals("Jason Bourne", p.name);
+    }
+    
+    public static class Person9 {
+        public String name;
+
+        @JsonbDateFormat("dd.MM.yyyy")
+        public LocalDate birthDate;
+
+        @JsonbNumberFormat("#0.00")
+        public BigDecimal salary;
+    }
+    
+    @Test
+    public void testDateNumberFormats1() {
+        Person9 p = new Person9();
+        p.name = "Jason Bourne";
+        p.birthDate = LocalDate.of(1999, 8, 7);
+        p.salary = new BigDecimal("123.45678");
+        Jsonb jsonb = JsonbBuilder.create();
+        String json = jsonb.toJson(p);
+        assertEquals("{\"birthDate\":\"07.08.1999\",\"name\":\"Jason Bourne\",\"salary\":\"123.46\"}", json);
+        
+        Person9 after = jsonb.fromJson("{\"birthDate\":\"07.08.1999\",\"name\":\"Jason Bourne\",\"salary\":\"123.46\"}", Person9.class);
+        assertEquals(p.name, after.name);
+        assertEquals(p.birthDate, after.birthDate);
+        assertEquals(new BigDecimal("123.46"), after.salary);
+    }
+    
+    public static class Person10 {
+        public String name;
+
+        public LocalDate birthDate;
+
+        public BigDecimal salary;
+    }
+    
+    @Test
+    public void testDateNumberFormats2() {
+        Person10 p = new Person10();
+        p.name = "Jason Bourne";
+        p.birthDate = LocalDate.of(1999, 8, 7);
+        p.salary = new BigDecimal("123.45678");
+        Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()//
+                .withDateFormat("dd.MM.yyyy", null)); // TODO: why no withNumberFormat?
+        String json = jsonb.toJson(p);
+        assertEquals("{\"birthDate\":\"07.08.1999\",\"name\":\"Jason Bourne\",\"salary\":123.45678}", json);
+        
+        Person9 after = jsonb.fromJson("{\"birthDate\":\"07.08.1999\",\"name\":\"Jason Bourne\",\"salary\":123.45678}", Person9.class);
+        assertEquals(p.name, after.name);
+        assertEquals(p.birthDate, after.birthDate);
+        assertEquals(p.salary, after.salary);
+    }
+    
+    public static class Customer {
+        private int id;
+        private String name;
+        private String organization;
+        private String position;
+        
+        public int getId() {
+            return id;
+        }
+        public void setId(int id) {
+            this.id = id;
+        }
+        public String getName() {
+            return name;
+        }
+        public void setName(String name) {
+            this.name = name;
+        }
+        public String getOrganization() {
+            return organization;
+        }
+        public void setOrganization(String organization) {
+            this.organization = organization;
+        }
+        public String getPosition() {
+            return position;
+        }
+        public void setPosition(String position) {
+            this.position = position;
+        }
+    }
+
+    public static class CustomerAnnotated {
+        @JsonbProperty("customer_id")
+        private int id;
+
+        @JsonbProperty("customer_name")
+        private String name;
+        
+        public int getId() {
+            return id;
+        }
+        public void setId(int id) {
+            this.id = id;
+        }
+        public String getName() {
+            return name;
+        }
+        public void setName(String name) {
+            this.name = name;
+        }
+    }
+
+    public static class CustomerAdapter implements JsonbAdapter<Customer, CustomerAnnotated> {
+        @Override
+        public CustomerAnnotated adaptToJson(Customer c) throws Exception {
+            CustomerAnnotated customer = new CustomerAnnotated();
+            customer.setId(c.getId());
+            customer.setName(c.getName());
+            return customer;
+        }
+
+        @Override
+        public Customer adaptFromJson(CustomerAnnotated adapted) throws Exception {
+            Customer customer = new Customer();
+            customer.setId(adapted.getId());
+            customer.setName(adapted.getName());
+            return customer;
+        }
+    }
+    
+    @Test
+    public void testAdapters1() {
+        // Create Jsonb with default configuration
+        Jsonb jsonb = JsonbBuilder.create();
+
+        // Create customer
+        Customer customer = new Customer();
+
+        customer.setId(1);
+        customer.setName("Jason Bourne");
+        customer.setOrganization("Super Agents");
+        customer.setPosition("Super Agent");
+
+        // Serialize
+        String json = jsonb.toJson(customer);
+        assertEquals("{\"id\":1,\"name\":\"Jason Bourne\",\"organization\":\"Super Agents\",\"position\":\"Super Agent\"}", json);
+    }
+    
+    @Test
+    public void testAdapters2() {
+     // Create custom configuration
+        JsonbConfig config = new JsonbConfig()
+            .withAdapters(new CustomerAdapter());
+
+        // Create Jsonb with custom configuration
+        Jsonb jsonb = JsonbBuilder.create(config);
+
+        // Create customer
+        Customer customer = new Customer();
+
+        customer.setId(1);
+        customer.setName("Jason Bourne");
+        customer.setOrganization("Super Agents");
+        customer.setPosition("Super Agent");
+
+        // Serialize
+        String json = jsonb.toJson(customer);
+        assertEquals("{\"customer_id\":1,\"customer_name\":\"Jason Bourne\"}", json);
+    }
+    
+    public static class CustomerSerializer implements JsonbSerializer<Customer> {
+        @Override
+        public void serialize(Customer customer, JsonGenerator generator, SerializationContext ctx) {
+            generator.writeStartObject();
+            generator.write("customer_id", customer.getId());
+            generator.write("customer_name", customer.getName());
+            generator.writeEnd();
+        }
+    }
+
+    public static class CustomerDeserializer implements JsonbDeserializer<Customer> {
+        @Override
+        public Customer deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
+            Customer customer = new Customer();
+            JsonParser.Event next;
+
+            // Moving parser by hand looking for customer_id and customer_name properties
+            while ((next = parser.next()) != JsonParser.Event.END_OBJECT) {
+                if (next == JsonParser.Event.KEY_NAME) {
+                    String jsonKeyName = parser.getString();
+
+                    // Move to json value
+                    parser.next();
+
+                    if ("customer_id".equals(jsonKeyName)) {
+                        customer.setId(parser.getInt());
+                    } else if ("customer_name".equals(jsonKeyName)) {
+                        customer.setName(parser.getString());
+                    }
+                }
+            }
+            return customer;
+        }
+    }
+    
+    @Test
+    public void testSerializerDeserializer() {
+        // Create pojo
+        Customer customer = new Customer();
+        customer.setId(1);
+        customer.setName("Freddie");
+        customer.setOrganization("Super Agents");
+        customer.setPosition("Super Agent");
+
+        // Also configurable with @JsonbSerializer / JsonbDeserializer on properties and class.
+        JsonbConfig config = new JsonbConfig()
+                .withSerializers(new CustomerSerializer())
+                .withDeserializers(new CustomerDeserializer());
+
+        Jsonb jsonb = JsonbBuilder.create(config);
+        String json = jsonb.toJson(customer);
+        assertEquals("{\"customer_id\":1,\"customer_name\":\"Freddie\"}", json);
+        
+        Customer result = jsonb.fromJson(json, Customer.class);
+        assertEquals(customer.getId(), result.getId());
+        assertEquals(customer.getName(), result.getName());
+        assertNull(result.getOrganization());
+        assertNull(result.getPosition());
+    }
+}
diff --git a/src/test/java/org/eclipse/yasson/jsonpsubstitution/PreinstantiatedJsonpTest.java b/src/test/java/org/eclipse/yasson/jsonpsubstitution/PreinstantiatedJsonpTest.java
index 63337b0..d72b799 100644
--- a/src/test/java/org/eclipse/yasson/jsonpsubstitution/PreinstantiatedJsonpTest.java
+++ b/src/test/java/org/eclipse/yasson/jsonpsubstitution/PreinstantiatedJsonpTest.java
@@ -9,6 +9,7 @@
  ******************************************************************************/
 package org.eclipse.yasson.jsonpsubstitution;
 
+import org.eclipse.yasson.Assertions;
 import org.eclipse.yasson.JsonBindingProvider;
 import org.eclipse.yasson.TestTypeToken;
 import org.eclipse.yasson.YassonJsonb;
@@ -165,12 +166,7 @@
         generator.writeStartObject();
         //key not written
 
-        try {
-            jsonb.toJson(dog, generator);
-            Assert.fail("JsonbException not thrown");
-        } catch (JsonbException e) {
-            //OK
-        }
+        Assertions.shouldFail(() -> jsonb.toJson(dog, generator));
     }
 
     @Test