diff --git a/api/src/main/java/io/cloudevents/v1/Accessor.java b/api/src/main/java/io/cloudevents/v1/Accessor.java
new file mode 100644
index 00000000..b08d34c7
--- /dev/null
+++ b/api/src/main/java/io/cloudevents/v1/Accessor.java
@@ -0,0 +1,44 @@
+package io.cloudevents.v1;
+
+import java.util.Collection;
+import java.util.Objects;
+
+import io.cloudevents.Attributes;
+import io.cloudevents.CloudEvent;
+import io.cloudevents.extensions.ExtensionFormat;
+import io.cloudevents.fun.ExtensionFormatAccessor;
+
+/**
+ *
+ * @author fabiojose
+ * @version 1.0
+ */
+public class Accessor {
+
+ /**
+ * To get access the set of {@link ExtensionFormat} inside the
+ * event.
+ *
+ *
+ *
+ * This method follow the signature of
+ * {@link ExtensionFormatAccessor#extensionsOf(CloudEvent)}
+ *
+ * @param cloudEvent
+ * @throws IllegalArgumentException When argument is not an instance
+ * of {@link CloudEventImpl}
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public static Collection
+ extensionsOf(CloudEvent cloudEvent) {
+ Objects.requireNonNull(cloudEvent);
+
+ if(cloudEvent instanceof CloudEventImpl) {
+ CloudEventImpl impl = (CloudEventImpl)cloudEvent;
+ return impl.getExtensionsFormats();
+ }
+
+ throw new IllegalArgumentException("Invalid instance type: "
+ + cloudEvent.getClass());
+ }
+}
diff --git a/api/src/main/java/io/cloudevents/v1/AttributesImpl.java b/api/src/main/java/io/cloudevents/v1/AttributesImpl.java
new file mode 100644
index 00000000..412b4048
--- /dev/null
+++ b/api/src/main/java/io/cloudevents/v1/AttributesImpl.java
@@ -0,0 +1,183 @@
+/**
+ * Copyright 2019 The CloudEvents Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.cloudevents.v1;
+
+import java.net.URI;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+import io.cloudevents.Attributes;
+import io.cloudevents.json.ZonedDateTimeDeserializer;
+
+/**
+ *
+ * @author fabiojose
+ * @version 1.0
+ */
+public class AttributesImpl implements Attributes {
+
+ @NotBlank
+ private final String id;
+
+ @NotNull
+ private final URI source;
+
+ @NotBlank
+ @Pattern(regexp = "1\\.0")
+ private final String specversion;
+
+ @NotBlank
+ private final String type;
+
+ private final String datacontenttype;
+
+ private final URI dataschema;
+
+ @Size(min = 1)
+ private final String subject;
+
+ @JsonDeserialize(using = ZonedDateTimeDeserializer.class)
+ private final ZonedDateTime time;
+
+ public AttributesImpl(String id, URI source, String specversion,
+ String type, String datacontenttype,
+ URI dataschema, String subject, ZonedDateTime time) {
+
+ this.id = id;
+ this.source = source;
+ this.specversion = specversion;
+ this.type = type;
+ this.datacontenttype = datacontenttype;
+ this.dataschema = dataschema;
+ this.subject = subject;
+ this.time = time;
+ }
+
+ @Override
+ public Optional getMediaType() {
+ return getDatacontenttype();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public URI getSource() {
+ return source;
+ }
+
+ public String getSpecversion() {
+ return specversion;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Optional getDatacontenttype() {
+ return Optional.ofNullable(datacontenttype);
+ }
+
+ public Optional getDataschema() {
+ return Optional.ofNullable(dataschema);
+ }
+
+ public Optional getSubject() {
+ return Optional.ofNullable(subject);
+ }
+
+ public Optional getTime() {
+ return Optional.ofNullable(time);
+ }
+
+ @Override
+ public String toString() {
+ return "AttibutesImpl [id=" + id + ", source=" + source
+ + ", specversion=" + specversion + ", type=" + type
+ + ", datacontenttype=" + datacontenttype + ", dataschema="
+ + dataschema + ", subject=" + subject
+ + ", time=" + time + "]";
+ }
+
+ /**
+ * Used by the Jackson framework to unmarshall.
+ */
+ @JsonCreator
+ public static AttributesImpl build(
+ @JsonProperty("id") String id,
+ @JsonProperty("source") URI source,
+ @JsonProperty("specversion") String specversion,
+ @JsonProperty("type") String type,
+ @JsonProperty("datacontenttype") String datacontenttype,
+ @JsonProperty("dataschema") URI dataschema,
+ @JsonProperty("subject") String subject,
+ @JsonProperty("time") ZonedDateTime time) {
+
+ return new AttributesImpl(id, source, specversion, type,
+ datacontenttype, dataschema, subject, time);
+ }
+
+ /**
+ * Creates the marshaller instance to marshall {@link AttributesImpl} as
+ * a {@link Map} of strings
+ */
+ public static Map marshal(AttributesImpl attributes) {
+ Objects.requireNonNull(attributes);
+ Map result = new HashMap<>();
+
+ result.put(ContextAttributes.id.name(),
+ attributes.getId());
+ result.put(ContextAttributes.source.name(),
+ attributes.getSource().toString());
+ result.put(ContextAttributes.specversion.name(),
+ attributes.getSpecversion());
+ result.put(ContextAttributes.type.name(),
+ attributes.getType());
+
+ attributes.getDatacontenttype().ifPresent(dct -> {
+ result.put(ContextAttributes.datacontenttype.name(), dct);
+ });
+
+ attributes.getDataschema().ifPresent(dataschema -> {
+ result.put(ContextAttributes.dataschema.name(),
+ dataschema.toString());
+ });
+
+ attributes.getSubject().ifPresent(subject -> {
+ result.put(ContextAttributes.subject.name(), subject);
+ });
+
+ attributes.getTime().ifPresent(time -> {
+ result.put(ContextAttributes.time.name(),
+ time.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
+ });
+
+ return result;
+ }
+}
diff --git a/api/src/main/java/io/cloudevents/v1/CloudEventBuilder.java b/api/src/main/java/io/cloudevents/v1/CloudEventBuilder.java
new file mode 100644
index 00000000..39a8526a
--- /dev/null
+++ b/api/src/main/java/io/cloudevents/v1/CloudEventBuilder.java
@@ -0,0 +1,202 @@
+package io.cloudevents.v1;
+
+import static java.lang.String.format;
+
+import java.net.URI;
+import java.time.ZonedDateTime;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
+
+import io.cloudevents.CloudEvent;
+import io.cloudevents.extensions.ExtensionFormat;
+import io.cloudevents.fun.EventBuilder;
+
+/**
+ *
+ * @author fabiojose
+ * @version 1.0
+ */
+public class CloudEventBuilder implements
+ EventBuilder {
+
+ private CloudEventBuilder() {}
+
+ private static Validator VALIDATOR;
+
+ public static final String SPEC_VERSION = "1.0";
+ private static final String MESSAGE_SEPARATOR = ", ";
+ private static final String MESSAGE = "'%s' %s";
+ private static final String ERR_MESSAGE = "invalid payload: %s";
+
+ private String id;
+ private URI source;
+
+ private String type;
+ private String datacontenttype;
+ private URI dataschema;
+ private String subject;
+ private ZonedDateTime time;
+
+ private T data;
+
+ private final Set extensions = new HashSet<>();
+
+ private static Validator getValidator() {
+ if(null== VALIDATOR) {
+ VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
+ }
+ return VALIDATOR;
+ }
+
+ /**
+ * Gets a brand new builder instance
+ * @param The 'data' type
+ */
+ public static CloudEventBuilder builder() {
+ return new CloudEventBuilder();
+ }
+
+ /**
+ * Builder with base event to copy attributes
+ * @param The 'data' type
+ * @param base The base event to copy attributes
+ */
+ public static CloudEventBuilder builder(
+ CloudEvent base) {
+ Objects.requireNonNull(base);
+
+ CloudEventBuilder result = new CloudEventBuilder<>();
+
+ AttributesImpl attributes = base.getAttributes();
+
+ result
+ .withId(attributes.getId())
+ .withSource(attributes.getSource())
+ .withType(attributes.getType());
+
+ attributes.getDataschema().ifPresent((schema) -> {
+ result.withDataschema(schema);
+ });
+
+ attributes.getDatacontenttype().ifPresent(dc -> {
+ result.withDatacontenttype(dc);
+ });
+
+ attributes.getSubject().ifPresent(subject -> {
+ result.withSubject(subject);
+ });
+
+ attributes.getTime().ifPresent(time -> {
+ result.withTime(time);
+ });
+
+ Accessor.extensionsOf(base)
+ .forEach(extension -> {
+ result.withExtension(extension);
+ });
+
+ base.getData().ifPresent(data -> {
+ result.withData(data);
+ });
+
+ return result;
+ }
+
+ @Override
+ public CloudEvent build(T data,
+ AttributesImpl attributes,
+ Collection extensions) {
+
+ return null;
+ }
+
+ /**
+ *
+ * @return An new {@link CloudEvent} immutable instance
+ * @throws IllegalStateException When there are specification constraints
+ * violations
+ */
+ public CloudEventImpl build() {
+
+ AttributesImpl attributes = new AttributesImpl(id, source, SPEC_VERSION, type,
+ datacontenttype, dataschema, subject, time);
+
+ CloudEventImpl cloudEvent =
+ new CloudEventImpl(attributes, data, extensions);
+
+ Set> violations =
+ getValidator().validate(cloudEvent);
+
+ violations.addAll(getValidator().validate(cloudEvent.getAttributes()));
+
+ final String errs =
+ violations.stream()
+ .map(v -> format(MESSAGE, v.getPropertyPath(), v.getMessage()))
+ .collect(Collectors.joining(MESSAGE_SEPARATOR));
+
+ Optional.ofNullable(
+ "".equals(errs) ? null : errs
+
+ ).ifPresent((e) -> {
+ throw new IllegalStateException(format(ERR_MESSAGE, e));
+ });
+
+ return cloudEvent;
+ }
+
+
+ public CloudEventBuilder withId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public CloudEventBuilder withSource(URI source) {
+ this.source = source;
+ return this;
+ }
+
+ public CloudEventBuilder withType(String type) {
+ this.type = type;
+ return this;
+ }
+
+ public CloudEventBuilder withDataschema(URI dataschema) {
+ this.dataschema = dataschema;
+ return this;
+ }
+
+ public CloudEventBuilder withDatacontenttype(
+ String datacontenttype) {
+ this.datacontenttype = datacontenttype;
+ return this;
+ }
+
+ public CloudEventBuilder withSubject(
+ String subject) {
+ this.subject = subject;
+ return this;
+ }
+
+ public CloudEventBuilder withTime(ZonedDateTime time) {
+ this.time = time;
+ return this;
+ }
+
+ public CloudEventBuilder withData(T data) {
+ this.data = data;
+ return this;
+ }
+
+ public CloudEventBuilder withExtension(ExtensionFormat extension) {
+ this.extensions.add(extension);
+ return this;
+ }
+}
diff --git a/api/src/main/java/io/cloudevents/v1/CloudEventImpl.java b/api/src/main/java/io/cloudevents/v1/CloudEventImpl.java
new file mode 100644
index 00000000..8e5d2d3a
--- /dev/null
+++ b/api/src/main/java/io/cloudevents/v1/CloudEventImpl.java
@@ -0,0 +1,135 @@
+/**
+ * Copyright 2019 The CloudEvents Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.cloudevents.v1;
+
+import java.net.URI;
+import java.time.ZonedDateTime;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.validation.constraints.NotNull;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+
+import io.cloudevents.CloudEvent;
+import io.cloudevents.extensions.ExtensionFormat;
+import io.cloudevents.extensions.InMemoryFormat;
+
+/**
+ *
+ * @author fabiojose
+ * @version 1.0
+ */
+@JsonInclude(value = Include.NON_ABSENT)
+public class CloudEventImpl implements CloudEvent {
+
+ @JsonIgnore
+ @NotNull
+ private final AttributesImpl attributes;
+
+ private final T data;
+
+ @NotNull
+ private final Map extensions;
+
+ private final Set extensionsFormats;
+
+ CloudEventImpl(AttributesImpl attributes, T data,
+ Set extensions){
+ this.attributes = attributes;
+ this.data = data;
+
+ this.extensions = extensions.stream()
+ .map(ExtensionFormat::memory)
+ .collect(Collectors.toMap(InMemoryFormat::getKey,
+ InMemoryFormat::getValue));
+
+ this.extensionsFormats = extensions;
+ }
+
+ /**
+ * Used by the {@link Accessor} to access the set of {@link ExtensionFormat}
+ */
+ Set getExtensionsFormats() {
+ return extensionsFormats;
+ }
+
+ @JsonUnwrapped
+ @Override
+ public AttributesImpl getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ public Optional getData() {
+ return Optional.ofNullable(data);
+ }
+
+ @JsonAnyGetter
+ @Override
+ public Map getExtensions() {
+ return Collections.unmodifiableMap(extensions);
+ }
+
+ /**
+ * The unique method that allows mutation. Used by
+ * Jackson Framework to inject the extensions.
+ *
+ * @param name Extension name
+ * @param value Extension value
+ */
+ @JsonAnySetter
+ void addExtension(String name, Object value) {
+ extensions.put(name, value);
+ }
+
+ /**
+ * Used by the Jackson Framework to unmarshall.
+ */
+ @JsonCreator
+ public static CloudEventImpl build(
+ @JsonProperty("id") String id,
+ @JsonProperty("source") URI source,
+ @JsonProperty("specversion") String specversion,
+ @JsonProperty("type") String type,
+ @JsonProperty("datacontenttype") String datacontenttype,
+ @JsonProperty("dataschema") URI dataschema,
+ @JsonProperty("subject") String subject,
+ @JsonProperty("time") ZonedDateTime time,
+ @JsonProperty("data") T data){
+
+ return CloudEventBuilder.builder()
+ .withId(id)
+ .withSource(source)
+ .withType(type)
+ .withTime(time)
+ .withDataschema(dataschema)
+ .withDatacontenttype(datacontenttype)
+ .withData(data)
+ .withSubject(subject)
+ .build();
+ }
+}
diff --git a/api/src/main/java/io/cloudevents/v1/ContextAttributes.java b/api/src/main/java/io/cloudevents/v1/ContextAttributes.java
new file mode 100644
index 00000000..1f2a3d64
--- /dev/null
+++ b/api/src/main/java/io/cloudevents/v1/ContextAttributes.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2019 The CloudEvents Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.cloudevents.v1;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * @author fabiojose
+ * @version 1.0
+ */
+public enum ContextAttributes {
+
+ id,
+ source,
+ specversion,
+ type,
+ datacontenttype,
+ dataschema,
+ subject,
+ time;
+
+ public static final List VALUES =
+ Arrays.asList(ContextAttributes.values())
+ .stream()
+ .map(Enum::name)
+ .collect(Collectors.toList());
+}
diff --git a/api/src/test/java/io/cloudevents/v1/AccessorTest.java b/api/src/test/java/io/cloudevents/v1/AccessorTest.java
new file mode 100644
index 00000000..7a07f623
--- /dev/null
+++ b/api/src/test/java/io/cloudevents/v1/AccessorTest.java
@@ -0,0 +1,136 @@
+/**
+ * Copyright 2019 The CloudEvents Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.cloudevents.v1;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.Collection;
+
+import org.junit.Test;
+
+import io.cloudevents.extensions.DistributedTracingExtension;
+import io.cloudevents.extensions.ExtensionFormat;
+import io.cloudevents.extensions.InMemoryFormat;
+import io.cloudevents.v1.Accessor;
+import io.cloudevents.v1.CloudEventBuilder;
+import io.cloudevents.v1.CloudEventImpl;
+
+/**
+ *
+ * @author fabiojose
+ *
+ */
+public class AccessorTest {
+ @Test
+ public void should_empty_collection_when_no_extensions() {
+ // setup
+ CloudEventImpl